Compare commits

...

10 Commits

Author SHA1 Message Date
Ross Scroggs
2fc8c8d718 gcpdetails/scopes cleanup #1891 2026-03-19 07:22:14 -07:00
Ross Scroggs
a3e5f7b504 gcpdetails/scopes cleanup #1891
Some checks failed
Build and test GAM / build (false, build, 1, Build Intel Ubuntu Jammy, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (false, build, 10, Build x86_64 macOS 15, macos-15-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 11, Build x86_64 macOS 26, macos-26-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 12, Build Arm MacOS 26, macos-26) (push) Has been cancelled
Build and test GAM / build (false, build, 13, Build Intel Windows, windows-2025-vs2026) (push) Has been cancelled
Build and test GAM / build (false, build, 14, Build Arm Windows, windows-11-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 2, Build Intel Ubuntu Noble, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (false, build, 3, Build Arm Ubuntu Noble, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 4, Build Arm Ubuntu Jammy, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 5, Build Intel StaticX Legacy, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 6, Build Arm StaticX Legacy, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 8, Build Arm MacOS 14, macos-14) (push) Has been cancelled
Build and test GAM / build (false, build, 9, Build Arm MacOS 15, macos-15) (push) Has been cancelled
Build and test GAM / build (false, test, 15, Test Python 3.10, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (false, test, 16, Test Python 3.11, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (false, test, 17, Test Python 3.12, ubuntu-24.04, 3.12) (push) Has been cancelled
Build and test GAM / build (false, test, 18, Test Python 3.13, ubuntu-24.04, 3.13) (push) Has been cancelled
Build and test GAM / build (false, test, 19, Test Python 3.15-dev, ubuntu-24.04, 3.15-dev) (push) Has been cancelled
Build and test GAM / build (true, test, 20, Test Python 3.14 freethread, ubuntu-24.04, 3.14) (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
2026-03-18 17:47:43 -07:00
Ross Scroggs
4137b3b77b gcpdetails/scopes cleanup #1891
Some checks failed
Build and test GAM / build (false, build, 1, Build Intel Ubuntu Jammy, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (false, build, 10, Build x86_64 macOS 15, macos-15-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 11, Build x86_64 macOS 26, macos-26-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 12, Build Arm MacOS 26, macos-26) (push) Has been cancelled
Build and test GAM / build (false, build, 13, Build Intel Windows, windows-2025-vs2026) (push) Has been cancelled
Build and test GAM / build (false, build, 14, Build Arm Windows, windows-11-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 2, Build Intel Ubuntu Noble, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (false, build, 3, Build Arm Ubuntu Noble, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 4, Build Arm Ubuntu Jammy, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 5, Build Intel StaticX Legacy, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 6, Build Arm StaticX Legacy, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 8, Build Arm MacOS 14, macos-14) (push) Has been cancelled
Build and test GAM / build (false, build, 9, Build Arm MacOS 15, macos-15) (push) Has been cancelled
Build and test GAM / build (false, test, 15, Test Python 3.10, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (false, test, 16, Test Python 3.11, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (false, test, 17, Test Python 3.12, ubuntu-24.04, 3.12) (push) Has been cancelled
Build and test GAM / build (false, test, 18, Test Python 3.13, ubuntu-24.04, 3.13) (push) Has been cancelled
Build and test GAM / build (false, test, 19, Test Python 3.15-dev, ubuntu-24.04, 3.15-dev) (push) Has been cancelled
Build and test GAM / build (true, test, 20, Test Python 3.14 freethread, ubuntu-24.04, 3.14) (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Push wiki / pushwiki (push) Has been cancelled
2026-03-18 17:22:57 -07:00
Ross Scroggs
ce47c9bc7c gcpdetails/scopes cleanup #1891 2026-03-18 16:39:20 -07:00
Ross Scroggs
d302563045 Merge branch 'main' of https://github.com/GAM-team/GAM 2026-03-18 13:19:23 -07:00
Jay Lee
c5f4bb18fa Merge branch 'main' of https://github.com/GAM-team/GAM 2026-03-18 20:11:57 +00:00
Jay Lee
0fdcab4c4f Initial gcpdetails on print token. #1891 2026-03-18 20:11:49 +00:00
Ross Scroggs
130a245906 Added gam <UserTypeEntity> sendreply 2026-03-18 10:50:49 -07:00
Ross Scroggs
d8bf368c92 Added gam <UserTypeEntity> sendreply 2026-03-18 10:50:37 -07:00
Ross Scroggs
e7b238e85e Update Send-Email.md
Some checks failed
Push wiki / pushwiki (push) Has been cancelled
Build and test GAM / build (false, build, 1, Build Intel Ubuntu Jammy, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (false, build, 10, Build x86_64 macOS 15, macos-15-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 11, Build x86_64 macOS 26, macos-26-intel) (push) Has been cancelled
Build and test GAM / build (false, build, 12, Build Arm MacOS 26, macos-26) (push) Has been cancelled
Build and test GAM / build (false, build, 13, Build Intel Windows, windows-2025-vs2026) (push) Has been cancelled
Build and test GAM / build (false, build, 14, Build Arm Windows, windows-11-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 2, Build Intel Ubuntu Noble, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (false, build, 3, Build Arm Ubuntu Noble, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 4, Build Arm Ubuntu Jammy, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (false, build, 5, Build Intel StaticX Legacy, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 6, Build Arm StaticX Legacy, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (false, build, 8, Build Arm MacOS 14, macos-14) (push) Has been cancelled
Build and test GAM / build (false, build, 9, Build Arm MacOS 15, macos-15) (push) Has been cancelled
Build and test GAM / build (false, test, 15, Test Python 3.10, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (false, test, 16, Test Python 3.11, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (false, test, 17, Test Python 3.12, ubuntu-24.04, 3.12) (push) Has been cancelled
Build and test GAM / build (false, test, 18, Test Python 3.13, ubuntu-24.04, 3.13) (push) Has been cancelled
Build and test GAM / build (false, test, 19, Test Python 3.15-dev, ubuntu-24.04, 3.15-dev) (push) Has been cancelled
Build and test GAM / build (true, test, 20, Test Python 3.14 freethread, ubuntu-24.04, 3.14) (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
Check for Google Root CA Updates / check-certs (push) Has been cancelled
2026-03-18 07:28:02 -07:00
9 changed files with 340 additions and 144 deletions

View File

@@ -4926,10 +4926,10 @@ gam print schema|schemas [todrive <ToDriveAttribute>*]
gam sendemail [recipient|to] <RecipientEntity> gam sendemail [recipient|to] <RecipientEntity>
[from <EmailAddress>] [mailbox <EmailAddress>] [replyto <EmailAddress>] [from <EmailAddress>] [mailbox <EmailAddress>] [replyto <EmailAddress>]
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage] [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
[subject <String>] [<MessageContent>] [subject <String>] [<MessageContent>] [html [<Boolean>]]
(replace <Tag> <String>)* (replace <Tag> <String>)*
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)* (replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)*
[html [<Boolean>]] (attach <FileName> [charset <Charset>])* (attach <FileName> [charset <Charset>])*
(embedimage <FileName> <String>)* (embedimage <FileName> <String>)*
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>] [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)* (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
@@ -4937,10 +4937,10 @@ gam sendemail [recipient|to] <RecipientEntity>
gam <UserTypeEntity> sendemail recipient|to <RecipientEntity> gam <UserTypeEntity> sendemail recipient|to <RecipientEntity>
[replyto <EmailAddress>] [replyto <EmailAddress>]
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage] [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
[subject <String>] [<MessageContent>] [subject <String>] [<MessageContent>] [html [<Boolean>]]
(replace <Tag> <String>)* (replace <Tag> <String>)*
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)* (replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)*
[html [<Boolean>]] (attach <FileName> [charset <Charset>])* (attach <FileName> [charset <Charset>])*
(embedimage <FileName> <String>)* (embedimage <FileName> <String>)*
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>] [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)* (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
@@ -4948,14 +4948,21 @@ gam <UserTypeEntity> sendemail recipient|to <RecipientEntity>
gam <UserTypeEntity> sendemail from <EmailAddress> gam <UserTypeEntity> sendemail from <EmailAddress>
[replyto <EmailAddress>] [replyto <EmailAddress>]
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage] [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
[subject <String>] [<MessageContent>] [subject <String>] [<MessageContent>] [html [<Boolean>]]
(replace <Tag> <String>)* (replace <Tag> <String>)*
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)* (replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)*
[html [<Boolean>]] (attach <FileName> [charset <Charset>])* (attach <FileName> [charset <Charset>])*
(embedimage <FileName> <String>)* (embedimage <FileName> <String>)*
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>] [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)* (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
[threadid <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 # Shared Drives - Administrator
@@ -8907,17 +8914,17 @@ gam <UserTypeEntity> delete tokens clientid <ClientID>
gam <UserTypeEntity> print tokens|token [todrive <ToDriveAttribute>*] [clientid <ClientID>] gam <UserTypeEntity> print tokens|token [todrive <ToDriveAttribute>*] [clientid <ClientID>]
[usertokencounts|(aggregateusersby|orderby clientid|id|appname|displaytext)] [usertokencounts|(aggregateusersby|orderby clientid|id|appname|displaytext)]
[delimiter <Character>] [delimiter <Character>] [gcpdetails]
gam <UserTypeEntity> show tokens|token|3lo|oauth [clientid <ClientID>] gam <UserTypeEntity> show tokens|token|3lo|oauth [clientid <ClientID>]
[usertokencounts|(aggregateusersby|orderby clientid|id|appname|displaytext)] [usertokencounts|(aggregateusersby|orderby clientid|id|appname|displaytext)]
[delimiter <Character>] [delimiter <Character>] [gcpdetails]
gam print tokens|token [todrive <ToDriveAttribute>*] [clientid <ClientID>] gam print tokens|token [todrive <ToDriveAttribute>*] [clientid <ClientID>]
[usertokencounts|(aggregateusersby|orderby clientid|id|appname|displaytext)] [usertokencounts|(aggregateusersby|orderby clientid|id|appname|displaytext)]
[delimiter <Character>] [delimiter <Character>] [gcpdetails]
[<UserTypeEntity>] [<UserTypeEntity>]
gam show tokens|token [clientid <ClientID>] gam show tokens|token [clientid <ClientID>]
[usertokencounts|(aggregateusersby|orderby clientid|id|appname|displaytext)] [usertokencounts|(aggregateusersby|orderby clientid|id|appname|displaytext)]
[delimiter <Character>] [delimiter <Character>] [gcpdetails]
[<UserTypeEntity>] [<UserTypeEntity>]
# Users - YouTube # Users - YouTube

View File

@@ -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 7.36.02
Added option `threadid <String>` to `gam [<UserTypeEntity>] sendemail` that causes Gmail to recognize the message Added option `threadid <String>` to `gam [<UserTypeEntity>] sendemail` that causes Gmail to recognize the message

View File

@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
""" """
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>' __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)' __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
# pylint: disable=wrong-import-position # 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])) httpObj = transportAuthorizedHttp(credentials, http=getHttpObj(cache=GM.Globals[GM.CACHE_DIR]))
service = getService(api, httpObj) service = getService(api, httpObj)
if not GC.Values[GC.ENABLE_DASA]: if not GC.Values[GC.ENABLE_DASA]:
try: discovery_scopes = list(service._rootDesc.get('auth', {}).get('oauth2', {}).get('scopes', {}).keys())
API_Scopes = set(list(service._rootDesc['auth']['oauth2']['scopes'])) extra_scopes = API.EXTRA_SCOPES.get(api, [])
except KeyError: API_Scopes = set(discovery_scopes + extra_scopes)
if api == API.VAULT:
API_Scopes = set(API.VAULT_SCOPES)
elif api == API.BUSINESSACCOUNTMANAGEMENT:
API_Scopes = {API.BUSINESSACCOUNTMANAGEMENT_SCOPE}
else:
API_Scopes = set()
GM.Globals[GM.CURRENT_CLIENT_API] = api GM.Globals[GM.CURRENT_CLIENT_API] = api
GM.Globals[GM.CURRENT_CLIENT_API_SCOPES] = API_Scopes.intersection(GM.Globals[GM.CREDENTIALS_SCOPES]) 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]: 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 # Send an email
def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msgFrom=None, msgReplyTo=None, def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msgFrom=None, msgReplyTo=None,
html=False, charset=UTF8, attachments=None, embeddedImages=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): def checkResult(entityType, recipients):
if not recipients: if not recipients:
return return
@@ -7403,12 +7398,13 @@ def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msg
if mailBox is None: if mailBox is None:
mailBox = msgFromAddr mailBox = msgFromAddr
_, mailBoxAddr = cleanAddr(mailBox) _, mailBoxAddr = cleanAddr(mailBox)
action = Act.Get() parentAction = Act.Get()
Act.Set(Act.SENDEMAIL) Act.Set(action)
if not GC.Values[GC.SMTP_HOST]: if not GC.Values[GC.SMTP_HOST]:
if not clientAccess: if not clientAccess:
userId, gmail = buildGAPIServiceObject(API.GMAIL, mailBoxAddr) userId, gmail = buildGAPIServiceObject(API.GMAIL, mailBoxAddr)
if not gmail: if not gmail:
Act.Set(parentAction)
return return
else: else:
userId = mailBoxAddr userId = mailBoxAddr
@@ -7450,7 +7446,7 @@ def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msg
server.quit() server.quit()
except Exception: except Exception:
pass pass
Act.Set(action) Act.Set(parentAction)
def addFieldToFieldsList(fieldName, fieldsChoiceMap, fieldsList): def addFieldToFieldsList(fieldName, fieldsChoiceMap, fieldsList):
fields = fieldsChoiceMap[fieldName.lower()] fields = fieldsChoiceMap[fieldName.lower()]
@@ -10820,14 +10816,14 @@ def getScopesFromUser(scopesList, clientAccess, currentScopes=None):
numScopes = len(scopesList) numScopes = len(scopesList)
for a_scope in scopesList: for a_scope in scopesList:
oauth2_menu += f"[%%s] %2d) {a_scope['name']}" 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 += f' (supports {" and ".join(a_scope["subscopes"])})'
oauth2_menu += '\n' oauth2_menu += '\n'
oauth2_menu += ''' oauth2_menu += '''
Select an unselected scope [ ] by entering a number; yields [*] 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 optionally support readonly, enter a number and an 'r' to grant readonly access; yields [R]
For scopes that support action, enter a number and an 'a' to grant action-only access; yields [A] For scopes that optionally support actiononly, enter a number and an 'a' to grant actiononly access; yields [A]
Clear read-only access [R] or action-only access [A] from a scope by entering a number; yields [*] 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 [ ] Unselect a selected scope [*] by entering a number; yields [ ]
Select all default scopes by entering an 's'; yields [*] for default scopes, [ ] for others 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 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: for a_scope in scopesList:
selectedScopes[i] = ' ' selectedScopes[i] = ' '
possibleScope = a_scope['scope'] possibleScope = a_scope['scope']
subScopes = a_scope.get('subscopes', [])
for currentScope in currentScopes: for currentScope in currentScopes:
if currentScope == possibleScope: if currentScope == possibleScope:
selectedScopes[i] = '*' selectedScopes[i] = '*'
break break
if 'readonly' in a_scope['subscopes']: if 'readonly' in subScopes:
if currentScope == possibleScope+'.readonly': if currentScope == possibleScope+'.readonly':
selectedScopes[i] = 'R' selectedScopes[i] = 'R'
break break
if 'action' in a_scope['subscopes']: if 'actiononly' in subScopes:
if currentScope == possibleScope+'.action': if currentScope == possibleScope+'.action':
selectedScopes[i] = 'A' selectedScopes[i] = 'A'
break break
@@ -10867,13 +10864,14 @@ Continue to authorization by entering a 'c'
selectedScopes[i] = ' ' selectedScopes[i] = ' '
api = a_scope['api'] api = a_scope['api']
possibleScope = a_scope['scope'] possibleScope = a_scope['scope']
subScopes = a_scope.get('subscopes', [])
if api in currentScopes: if api in currentScopes:
if not isinstance(possibleScope, list): if not isinstance(possibleScope, list):
for scope in currentScopes[api]: for scope in currentScopes[api]:
if scope == possibleScope: if scope == possibleScope:
selectedScopes[i] = '*' selectedScopes[i] = '*'
break break
if 'readonly' in a_scope['subscopes']: if 'readonly' in subScopes:
if (scope == possibleScope+'.readonly') or (scope == a_scope.get('roscope')): if (scope == possibleScope+'.readonly') or (scope == a_scope.get('roscope')):
selectedScopes[i] = 'R' selectedScopes[i] = 'R'
break break
@@ -10914,12 +10912,12 @@ Continue to authorization by entering a 'c'
selection = int(selection) selection = int(selection)
if isinstance(selection, int) and selection < numScopes: if isinstance(selection, int) and selection < numScopes:
if mode == 'R': if mode == 'R':
if 'readonly' not in scopesList[selection]['subscopes']: if 'readonly' not in scopesList[selection].get('subscopes',[]):
sys.stdout.write(f'{ERROR_PREFIX}Scope {selection} does not support read-only mode!\n') sys.stdout.write(f'{ERROR_PREFIX}Scope {selection} does not support readonly mode!\n')
continue continue
elif mode == 'A': elif mode == 'A':
if 'action' not in scopesList[selection]['subscopes']: if 'actiononly' not in scopesList[selection].get('subscopes', []):
sys.stdout.write(f'{ERROR_PREFIX}Scope {selection} does not support action-only mode!\n') sys.stdout.write(f'{ERROR_PREFIX}Scope {selection} does not support actiononly mode!\n')
continue continue
elif selectedScopes[selection] != '*': elif selectedScopes[selection] != '*':
mode = '*' mode = '*'
@@ -11361,9 +11359,10 @@ def doOAuthCreate():
if uscope in {'openid', 'email', API.USERINFO_EMAIL_SCOPE, 'profile', API.USERINFO_PROFILE_SCOPE}: if uscope in {'openid', 'email', API.USERINFO_EMAIL_SCOPE, 'profile', API.USERINFO_PROFILE_SCOPE}:
continue continue
for scope in scopesList: for scope in scopesList:
subScopes = scope.get('subscopes', [])
if ((uscope == scope['scope']) or if ((uscope == scope['scope']) or
(uscope.endswith('.action') and 'action' in scope['subscopes']) or (uscope.endswith('.action') and 'actiononly' in subScopes) or
(uscope.endswith('.readonly') and 'readonly' in scope['subscopes'])): (uscope.endswith('.readonly') and 'readonly' in subScopes)):
scopes.append(uscope) scopes.append(uscope)
break break
else: else:
@@ -11996,12 +11995,13 @@ def getGCPOrg(crm, login_hint, login_domain):
try: try:
getorg = callGAPI(crm.organizations(), 'search', getorg = callGAPI(crm.organizations(), 'search',
throwReasons=[GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED], 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: except (GAPI.invalidArgument, GAPI.permissionDenied) as e:
entityActionFailedExit([Ent.USER, login_hint, Ent.DOMAIN, login_domain], str(e)) entityActionFailedExit([Ent.USER, login_hint, Ent.DOMAIN, login_domain], str(e))
try: try:
organization = getorg['organizations'][0]['name'] 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 return organization
except (KeyError, IndexError): except (KeyError, IndexError):
systemErrorExit(3, Msg.YOU_HAVE_NO_RIGHTS_TO_CREATE_PROJECTS_AND_YOU_ARE_NOT_A_SUPER_ADMIN) 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 [normalizeEmailAddressOrUID(emailAddress, noUid=True, noLower=True) for emailAddress in recipients]
return getNormalizedEmailAddressEntity(shlexSplit=True, noLower=True) 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] # [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
# [subject <String>] [<MessageContent>] # [subject <String>] [<MessageContent>] [html [<Boolean>]]
# (replace <Tag> <String>)* # (replace <Tag> <String>)*
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)* # (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
# [html [<Boolean>]] (attach <FileName> [charset <CharSet>])* # (attach <FileName> [charset <CharSet>])*
# (embedimage <FileName> <String>)* # (embedimage <FileName> <String>)*
# [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>] # [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)* # (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
# [threadid <String>] # [threadid <String>]
# gam <UserTypeEntity> sendemail recipient|to <RecipientEntity> [replyto <EmailAddress>] # gam <UserTypeEntity> sendemail recipient|to <RecipientEntity>
# [replyto <EmailAddress>]
# [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage] # [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
# [subject <String>] [<MessageContent>] # [subject <String>] [<MessageContent>] [html [<Boolean>]]
# (replace <Tag> <String>)* # (replace <Tag> <String>)*
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)* # (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
# [html [<Boolean>]] (attach <FileName> [charset <CharSet>])* # (attach <FileName> [charset <CharSet>])*
# (embedimage <FileName> <String>)* # (embedimage <FileName> <String>)*
# [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>] # [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)* # (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
# [threadid <String>] # [threadid <String>]
# gam <UserTypeEntity> sendemail from <EmailAddress> [replyto <EmailAddress>] # gam <UserTypeEntity> sendemail from <EmailAddress>
# [replyto <EmailAddress>]
# [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage] # [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
# [subject <String>] [<MessageContent>] # [subject <String>] [<MessageContent> ][html [<Boolean>]]
# (replace <Tag> <String>)* # (replace <Tag> <String>)*
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)* # (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
# [html [<Boolean>]] (attach <FileName> [charset <CharSet>])* # (attach <FileName> [charset <CharSet>])*
# (embedimage <FileName> <String>)* # (embedimage <FileName> <String>)*
# [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>] # [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)* # (<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) attachments=attachments, embeddedImages=embeddedImages, msgHeaders=msgHeaders, mailBox=mailBox, threadId=threadId)
Ind.Decrement() 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'] ADDRESS_FIELDS_PRINT_ORDER = ['contactName', 'organizationName', 'addressLine1', 'addressLine2', 'addressLine3', 'locality', 'region', 'postalCode', 'countryCode']
def _showCustomerAddressPhoneNumber(customerInfo): def _showCustomerAddressPhoneNumber(customerInfo):
@@ -72321,6 +72443,29 @@ def _printShowTokens(entityType, users):
Ind.Decrement() Ind.Decrement()
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) cd = buildGAPIObject(API.DIRECTORY)
csvPF = CSVPrintFile() if Act.csvFormat() else None csvPF = CSVPrintFile() if Act.csvFormat() else None
clientId = None clientId = None
@@ -72329,6 +72474,8 @@ def _printShowTokens(entityType, users):
delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER] delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER]
aggregateTokensById = {} aggregateTokensById = {}
tokenNameIdMap = None tokenNameIdMap = None
getGCPDetails = False
extra_titles = []
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
myarg = getArgument() myarg = getArgument()
if csvPF and myarg == 'todrive': if csvPF and myarg == 'todrive':
@@ -72345,6 +72492,9 @@ def _printShowTokens(entityType, users):
aggregateUsersBy = 'user' aggregateUsersBy = 'user'
elif myarg == 'delimiter': elif myarg == 'delimiter':
delimiter = getCharacter() delimiter = getCharacter()
elif myarg == 'gcpdetails':
getGCPDetails = True
extra_titles = ['project', 'internal']
elif not entityType: elif not entityType:
Cmd.Backup() Cmd.Backup()
entityType, users = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS) entityType, users = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS)
@@ -72354,9 +72504,9 @@ def _printShowTokens(entityType, users):
users = getItemsToModify(Cmd.ENTITY_ALL_USERS_NS, None) users = getItemsToModify(Cmd.ENTITY_ALL_USERS_NS, None)
if csvPF: if csvPF:
if not aggregateUsersBy: if not aggregateUsersBy:
csvPF.SetTitles(['user']+TOKENS_FIELDS_TITLES) csvPF.SetTitles(['user'] + TOKENS_FIELDS_TITLES + extra_titles)
elif aggregateUsersBy != 'user': elif aggregateUsersBy != 'user':
csvPF.SetTitles(TOKENS_AGGREGATE_FIELDS_TITLES) csvPF.SetTitles(TOKENS_AGGREGATE_FIELDS_TITLES + extra_titles)
else: else:
csvPF.SetTitles(['user', 'tokenCount']) csvPF.SetTitles(['user', 'tokenCount'])
else: else:
@@ -72364,6 +72514,13 @@ def _printShowTokens(entityType, users):
tokenTitle = TOKENS_TITLE_MAP[orderBy] tokenTitle = TOKENS_TITLE_MAP[orderBy]
else: else:
tokenTitle = TOKENS_TITLE_MAP[aggregateUsersBy] 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) fields = ','.join(TOKENS_FIELDS_TITLES)
i, count, users = getEntityArgument(users) i, count, users = getEntityArgument(users)
for user in users: for user in users:
@@ -72385,6 +72542,8 @@ def _printShowTokens(entityType, users):
GAPI.DOMAIN_CANNOT_USE_APIS, GAPI.BAD_REQUEST, GAPI.DOMAIN_CANNOT_USE_APIS, GAPI.BAD_REQUEST,
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED], GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
userKey=user, fields=f'items({fields})') userKey=user, fields=f'items({fields})')
if getGCPDetails:
get_gcp_info(results)
if not aggregateUsersBy: if not aggregateUsersBy:
if not csvPF: if not csvPF:
jcount = len(results) jcount = len(results)
@@ -80845,6 +81004,7 @@ USER_COMMANDS = {
'profile': (Act.SET, setProfile), 'profile': (Act.SET, setProfile),
'sendas': (Act.ADD, createUpdateSendAs), 'sendas': (Act.ADD, createUpdateSendAs),
'sendemail': (Act.SENDEMAIL, doSendEmail), 'sendemail': (Act.SENDEMAIL, doSendEmail),
'sendreply': (Act.SENDREPLY, doSendReply),
'signature': (Act.SET, setSignature), 'signature': (Act.SET, setSignature),
'signout': (Act.SIGNOUT, signoutTurnoff2SVUsers), 'signout': (Act.SIGNOUT, signoutTurnoff2SVUsers),
'turnoff2sv': (Act.TURNOFF2SV, signoutTurnoff2SVUsers), 'turnoff2sv': (Act.TURNOFF2SV, signoutTurnoff2SVUsers),

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2024 Ross Scroggs All Rights Reserved. # Copyright (C) 2026 Ross Scroggs All Rights Reserved.
# #
# All Rights Reserved. # All Rights Reserved.
# #
@@ -107,6 +107,7 @@ class GamAction():
SAVE = 'save' SAVE = 'save'
SEND = 'send' SEND = 'send'
SENDEMAIL = 'snem' SENDEMAIL = 'snem'
SENDREPLY = 'sner'
SET = 'set ' SET = 'set '
SETUP = 'setu' SETUP = 'setu'
SHARE = 'shar' SHARE = 'shar'
@@ -225,6 +226,7 @@ class GamAction():
SAVE: ['Saved', 'Save'], SAVE: ['Saved', 'Save'],
SEND: ['Sent', 'Send'], SEND: ['Sent', 'Send'],
SENDEMAIL: ['Email Sent', 'Send Email'], SENDEMAIL: ['Email Sent', 'Send Email'],
SENDREPLY: ['Reply Sent', 'Send Reply'],
SET: ['Set', 'Set'], SET: ['Set', 'Set'],
SETUP: ['Set Up', 'Set Up'], SETUP: ['Set Up', 'Set Up'],
SHARE: ['Shared', 'Share'], SHARE: ['Shared', 'Share'],

View File

@@ -54,6 +54,7 @@ CLOUDIDENTITY_POLICY = 'cloudidentitypolicy'
CLOUDIDENTITY_POLICY_BETA = 'cloudidentitypolicybeta' CLOUDIDENTITY_POLICY_BETA = 'cloudidentitypolicybeta'
CLOUDIDENTITY_USERINVITATIONS = 'cloudidentityuserinvitations' CLOUDIDENTITY_USERINVITATIONS = 'cloudidentityuserinvitations'
CLOUDRESOURCEMANAGER = 'cloudresourcemanager' CLOUDRESOURCEMANAGER = 'cloudresourcemanager'
CLOUDRESOURCEMANAGERV1 = 'cloudresourcemanagerv1'
CONTACTS = 'contacts' CONTACTS = 'contacts'
CONTACTDELEGATION = 'contactdelegation' CONTACTDELEGATION = 'contactdelegation'
DATATRANSFER = 'datatransfer' DATATRANSFER = 'datatransfer'
@@ -103,7 +104,6 @@ TASKS = 'tasks'
VAULT = 'vault' VAULT = 'vault'
YOUTUBE = 'youtube' YOUTUBE = 'youtube'
# #
BUSINESSACCOUNTMANAGEMENT_SCOPE = 'https://www.googleapis.com/auth/business.manage'
CHROMEVERSIONHISTORY_URL = 'https://versionhistory.googleapis.com/v1/chrome/platforms' CHROMEVERSIONHISTORY_URL = 'https://versionhistory.googleapis.com/v1/chrome/platforms'
DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive' DRIVE_SCOPE = 'https://www.googleapis.com/auth/drive'
DRIVE_FILE_SCOPE = 'https://www.googleapis.com/auth/drive.file' 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' STORAGE_READWRITE_SCOPE = 'https://www.googleapis.com/auth/devstorage.read_write'
USERINFO_EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email' # email USERINFO_EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email' # email
USERINFO_PROFILE_SCOPE = 'https://www.googleapis.com/auth/userinfo.profile' # profile 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 = [USERINFO_EMAIL_SCOPE, USERINFO_PROFILE_SCOPE]
REQUIRED_SCOPES_SET = set(REQUIRED_SCOPES) REQUIRED_SCOPES_SET = set(REQUIRED_SCOPES)
NUM_CLIENT_SCOPES_ERROR_LIMIT = 48 NUM_CLIENT_SCOPES_ERROR_LIMIT = 48
@@ -138,6 +137,21 @@ SCOPELESS_APIS = {
SERVICEACCOUNTLOOKUP, 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 = { APIS_NEEDING_ACCESS_TOKEN = {
CBCM: ['https://www.googleapis.com/auth/admin.directory.device.chromebrowsers'] 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: {'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_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'}, 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}, CONTACTS: {'name': 'Contacts API', 'version': 'v3', 'v2discovery': False},
CONTACTDELEGATION: {'name': 'Contact Delegation API', 'version': 'v1', 'v2discovery': True, 'localjson': True}, CONTACTDELEGATION: {'name': 'Contact Delegation API', 'version': 'v1', 'v2discovery': True, 'localjson': True},
DATATRANSFER: {'name': 'Data Transfer API', 'version': 'datatransfer_v1', 'v2discovery': True, 'mappedAPI': 'admin'}, DATATRANSFER: {'name': 'Data Transfer API', 'version': 'datatransfer_v1', 'v2discovery': True, 'mappedAPI': 'admin'},
@@ -305,9 +320,8 @@ READONLY = ['readonly',]
_CLIENT_SCOPES = [ _CLIENT_SCOPES = [
{'name': 'Business Account Management API', {'name': 'Business Account Management API',
'api': BUSINESSACCOUNTMANAGEMENT, 'api': BUSINESSACCOUNTMANAGEMENT,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': BUSINESSACCOUNTMANAGEMENT_SCOPE}, 'scope': EXTRA_SCOPES[BUSINESSACCOUNTMANAGEMENT]},
{'name': 'Calendar API', {'name': 'Calendar API',
'api': CALENDAR, 'api': CALENDAR,
'subscopes': READONLY, 'subscopes': READONLY,
@@ -316,21 +330,18 @@ _CLIENT_SCOPES = [
'api': CBCM, 'api': CBCM,
'subscopes': READONLY, 'subscopes': READONLY,
'scope': 'https://www.googleapis.com/auth/admin.directory.device.chromebrowsers'}, 'scope': 'https://www.googleapis.com/auth/admin.directory.device.chromebrowsers'},
{'name': 'Chrome Management API - read only', {'name': 'Chrome Management API - readonly',
'api': CHROMEMANAGEMENT, 'api': CHROMEMANAGEMENT,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/chrome.management.reports.readonly'}, '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, 'api': CHROMEMANAGEMENT_APPDETAILS,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/chrome.management.appdetails.readonly'}, 'scope': 'https://www.googleapis.com/auth/chrome.management.appdetails.readonly'},
{'name': 'Chrome Management API - Profiles', {'name': 'Chrome Management API - Profiles',
'api': CHROMEMANAGEMENT_CHROMEPROFILES, 'api': CHROMEMANAGEMENT_CHROMEPROFILES,
'subscopes': READONLY, 'subscopes': READONLY,
'scope': 'https://www.googleapis.com/auth/chrome.management.profiles'}, '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, 'api': CHROMEMANAGEMENT_TELEMETRY,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/chrome.management.telemetry.readonly'}, 'scope': 'https://www.googleapis.com/auth/chrome.management.telemetry.readonly'},
{'name': 'Chrome Policy API', {'name': 'Chrome Policy API',
'api': CHROMEPOLICY, 'api': CHROMEPOLICY,
@@ -342,7 +353,6 @@ _CLIENT_SCOPES = [
'scope': 'https://www.googleapis.com/auth/admin.chrome.printers'}, 'scope': 'https://www.googleapis.com/auth/admin.chrome.printers'},
{'name': 'Chrome Version History API', {'name': 'Chrome Version History API',
'api': CHROMEVERSIONHISTORY, 'api': CHROMEVERSIONHISTORY,
'subscopes': [],
'scope': ''}, 'scope': ''},
{'name': 'Classroom API - Courses', {'name': 'Classroom API - Courses',
'api': CLASSROOM, 'api': CLASSROOM,
@@ -370,11 +380,9 @@ _CLIENT_SCOPES = [
'scope': 'https://www.googleapis.com/auth/classroom.guardianlinks.students'}, 'scope': 'https://www.googleapis.com/auth/classroom.guardianlinks.students'},
{'name': 'Classroom API - Profile Emails', {'name': 'Classroom API - Profile Emails',
'api': CLASSROOM, 'api': CLASSROOM,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/classroom.profile.emails'}, 'scope': 'https://www.googleapis.com/auth/classroom.profile.emails'},
{'name': 'Classroom API - Profile Photos', {'name': 'Classroom API - Profile Photos',
'api': CLASSROOM, 'api': CLASSROOM,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/classroom.profile.photos'}, 'scope': 'https://www.googleapis.com/auth/classroom.profile.photos'},
{'name': 'Classroom API - Rosters', {'name': 'Classroom API - Rosters',
'api': CLASSROOM, 'api': CLASSROOM,
@@ -404,7 +412,6 @@ _CLIENT_SCOPES = [
'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'}, 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
{'name': 'Cloud Identity API - Policy Beta', {'name': 'Cloud Identity API - Policy Beta',
'api': CLOUDIDENTITY_POLICY_BETA, 'api': CLOUDIDENTITY_POLICY_BETA,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'}, 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
{'name': 'Cloud Identity API - User Invitations', {'name': 'Cloud Identity API - User Invitations',
@@ -413,17 +420,14 @@ _CLIENT_SCOPES = [
'scope': 'https://www.googleapis.com/auth/cloud-identity.userinvitations'}, 'scope': 'https://www.googleapis.com/auth/cloud-identity.userinvitations'},
{'name': 'Cloud Storage API (Read Only, Vault/Takeout Download, Cloud Storage)', {'name': 'Cloud Storage API (Read Only, Vault/Takeout Download, Cloud Storage)',
'api': STORAGEREAD, 'api': STORAGEREAD,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': STORAGE_READONLY_SCOPE}, 'scope': STORAGE_READONLY_SCOPE},
{'name': 'Cloud Storage API (Read/Write, Vault/Takeout Copy/Download, Cloud Storage)', {'name': 'Cloud Storage API (Read/Write, Vault/Takeout Copy/Download, Cloud Storage)',
'api': STORAGEWRITE, 'api': STORAGEWRITE,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': STORAGE_READWRITE_SCOPE}, 'scope': STORAGE_READWRITE_SCOPE},
{'name': 'Contacts API - Domain Shared Contacts', {'name': 'Contacts API - Domain Shared Contacts',
'api': CONTACTS, 'api': CONTACTS,
'subscopes': [],
'scope': 'https://www.google.com/m8/feeds'}, 'scope': 'https://www.google.com/m8/feeds'},
{'name': 'Contact Delegation API', {'name': 'Contact Delegation API',
'api': CONTACTDELEGATION, 'api': CONTACTDELEGATION,
@@ -451,7 +455,7 @@ _CLIENT_SCOPES = [
'scope': 'https://www.googleapis.com/auth/admin.directory.group'}, 'scope': 'https://www.googleapis.com/auth/admin.directory.group'},
{'name': 'Directory API - Mobile Devices Directory', {'name': 'Directory API - Mobile Devices Directory',
'api': DIRECTORY, 'api': DIRECTORY,
'subscopes': ['readonly', 'action'], 'subscopes': ['readonly', 'actiononly'],
'scope': 'https://www.googleapis.com/auth/admin.directory.device.mobile'}, 'scope': 'https://www.googleapis.com/auth/admin.directory.device.mobile'},
{'name': 'Directory API - Organizational Units', {'name': 'Directory API - Organizational Units',
'api': DIRECTORY, 'api': DIRECTORY,
@@ -471,7 +475,6 @@ _CLIENT_SCOPES = [
'scope': 'https://www.googleapis.com/auth/admin.directory.userschema'}, 'scope': 'https://www.googleapis.com/auth/admin.directory.userschema'},
{'name': 'Directory API - User Security', {'name': 'Directory API - User Security',
'api': DIRECTORY, 'api': DIRECTORY,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/admin.directory.user.security'}, 'scope': 'https://www.googleapis.com/auth/admin.directory.user.security'},
{'name': 'Directory API - Users', {'name': 'Directory API - Users',
'api': DIRECTORY, 'api': DIRECTORY,
@@ -479,24 +482,19 @@ _CLIENT_SCOPES = [
'scope': 'https://www.googleapis.com/auth/admin.directory.user'}, 'scope': 'https://www.googleapis.com/auth/admin.directory.user'},
{'name': 'Email Audit API', {'name': 'Email Audit API',
'api': EMAIL_AUDIT, 'api': EMAIL_AUDIT,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': 'https://apps-apis.google.com/a/feeds/compliance/audit/'}, 'scope': 'https://apps-apis.google.com/a/feeds/compliance/audit/'},
{'name': 'Groups Migration API', {'name': 'Groups Migration API',
'api': GROUPSMIGRATION, 'api': GROUPSMIGRATION,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/apps.groups.migration'}, 'scope': 'https://www.googleapis.com/auth/apps.groups.migration'},
{'name': 'Groups Settings API', {'name': 'Groups Settings API',
'api': GROUPSSETTINGS, 'api': GROUPSSETTINGS,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/apps.groups.settings'}, 'scope': 'https://www.googleapis.com/auth/apps.groups.settings'},
{'name': 'License Manager API', {'name': 'License Manager API',
'api': LICENSING, 'api': LICENSING,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/apps.licensing'}, 'scope': 'https://www.googleapis.com/auth/apps.licensing'},
{'name': 'People Directory API - read only', {'name': 'People Directory API - readonly',
'api': PEOPLE_DIRECTORY, 'api': PEOPLE_DIRECTORY,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/directory.readonly'}, 'scope': 'https://www.googleapis.com/auth/directory.readonly'},
{'name': 'People API', {'name': 'People API',
'api': PEOPLE, 'api': PEOPLE,
@@ -504,29 +502,31 @@ _CLIENT_SCOPES = [
'scope': PEOPLE_SCOPE}, 'scope': PEOPLE_SCOPE},
{'name': 'Pub / Sub API', {'name': 'Pub / Sub API',
'api': PUBSUB, 'api': PUBSUB,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': 'https://www.googleapis.com/auth/pubsub'}, 'scope': 'https://www.googleapis.com/auth/pubsub'},
{'name': 'Reports API - Audit Reports', {'name': 'Reports API - Audit Reports readonly',
'api': REPORTS, 'api': REPORTS,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/admin.reports.audit.readonly'}, 'scope': 'https://www.googleapis.com/auth/admin.reports.audit.readonly'},
{'name': 'Reports API - Usage Reports', {'name': 'Reports API - Usage Reports readonly',
'api': REPORTS, 'api': REPORTS,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/admin.reports.usage.readonly'}, 'scope': 'https://www.googleapis.com/auth/admin.reports.usage.readonly'},
{'name': 'Reseller API', {'name': 'Reseller API',
'api': RESELLER, 'api': RESELLER,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': 'https://www.googleapis.com/auth/apps.order'}, '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', {'name': 'Service Account Lookup pseudo-API',
'api': SERVICEACCOUNTLOOKUP, 'api': SERVICEACCOUNTLOOKUP,
'subscopes': [],
'scope': ''}, 'scope': ''},
{'name': 'Site Verification API', {'name': 'Site Verification API',
'api': SITEVERIFICATION, 'api': SITEVERIFICATION,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': 'https://www.googleapis.com/auth/siteverification'}, 'scope': 'https://www.googleapis.com/auth/siteverification'},
{'name': 'Vault API', {'name': 'Vault API',
@@ -538,30 +538,24 @@ _CLIENT_SCOPES = [
_COMMANDDATA_CLIENT_SCOPES = [ _COMMANDDATA_CLIENT_SCOPES = [
{'name': 'Drive API - commanddata_clientaccess', {'name': 'Drive API - commanddata_clientaccess',
'api': DRIVE3, 'api': DRIVE3,
'subscopes': [],
'scope': DRIVE_READONLY_SCOPE}, 'scope': DRIVE_READONLY_SCOPE},
{'name': 'Sheets API - commanddata_clientaccess', {'name': 'Sheets API - commanddata_clientaccess readonly',
'api': SHEETS, 'api': SHEETS,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/spreadsheets.readonly'}, 'scope': 'https://www.googleapis.com/auth/spreadsheets.readonly'},
] ]
_TODRIVE_CLIENT_SCOPES = [ _TODRIVE_CLIENT_SCOPES = [
{'name': 'Drive API - todrive_clientaccess', {'name': 'Drive API - todrive_clientaccess',
'api': DRIVE3, 'api': DRIVE3,
'subscopes': [],
'scope': DRIVE_SCOPE}, 'scope': DRIVE_SCOPE},
{'name': 'Drive File API - todrive_clientaccess', {'name': 'Drive File API - todrive_clientaccess',
'api': DRIVE3, 'api': DRIVE3,
'subscopes': [],
'scope': DRIVE_FILE_SCOPE}, 'scope': DRIVE_FILE_SCOPE},
{'name': 'Gmail API - todrive_clientaccess', {'name': 'Gmail API - todrive_clientaccess',
'api': GMAIL, 'api': GMAIL,
'subscopes': [],
'scope': GMAIL_SEND_SCOPE}, 'scope': GMAIL_SEND_SCOPE},
{'name': 'Sheets API - todrive_clientaccess', {'name': 'Sheets API - todrive_clientaccess',
'api': SHEETS, 'api': SHEETS,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/spreadsheets'}, 'scope': 'https://www.googleapis.com/auth/spreadsheets'},
] ]
@@ -570,11 +564,9 @@ OAUTH2SA_SCOPES = 'us_scopes'
_SVCACCT_SCOPES = [ _SVCACCT_SCOPES = [
{'name': 'AlertCenter API', {'name': 'AlertCenter API',
'api': ALERTCENTER, 'api': ALERTCENTER,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/apps.alerts'}, 'scope': 'https://www.googleapis.com/auth/apps.alerts'},
{'name': 'Analytics Admin API - read only', {'name': 'Analytics Admin API - readonly',
'api': ANALYTICS_ADMIN, 'api': ANALYTICS_ADMIN,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/analytics.readonly'}, 'scope': 'https://www.googleapis.com/auth/analytics.readonly'},
{'name': 'Calendar API', {'name': 'Calendar API',
'api': CALENDAR, 'api': CALENDAR,
@@ -611,11 +603,9 @@ _SVCACCT_SCOPES = [
'scope': 'https://www.googleapis.com/auth/chat.admin.spaces'}, 'scope': 'https://www.googleapis.com/auth/chat.admin.spaces'},
{'name': 'Chat API - Spaces Delete', {'name': 'Chat API - Spaces Delete',
'api': CHAT_SPACES_DELETE, 'api': CHAT_SPACES_DELETE,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/chat.delete'}, 'scope': 'https://www.googleapis.com/auth/chat.delete'},
{'name': 'Chat API - Spaces Delete Admin', {'name': 'Chat API - Spaces Delete Admin',
'api': CHAT_SPACES_DELETE_ADMIN, 'api': CHAT_SPACES_DELETE_ADMIN,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/chat.admin.delete'}, 'scope': 'https://www.googleapis.com/auth/chat.admin.delete'},
{'name': 'Classroom API - Course Announcements', {'name': 'Classroom API - Course Announcements',
'api': CLASSROOM, 'api': CLASSROOM,
@@ -635,11 +625,9 @@ _SVCACCT_SCOPES = [
'scope': 'https://www.googleapis.com/auth/classroom.coursework.students'}, 'scope': 'https://www.googleapis.com/auth/classroom.coursework.students'},
{'name': 'Classroom API - Profile Emails', {'name': 'Classroom API - Profile Emails',
'api': CLASSROOM, 'api': CLASSROOM,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/classroom.profile.emails'}, 'scope': 'https://www.googleapis.com/auth/classroom.profile.emails'},
{'name': 'Classroom API - Profile Photos', {'name': 'Classroom API - Profile Photos',
'api': CLASSROOM, 'api': CLASSROOM,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/classroom.profile.photos'}, 'scope': 'https://www.googleapis.com/auth/classroom.profile.photos'},
{'name': 'Classroom API - Rosters', {'name': 'Classroom API - Rosters',
'api': CLASSROOM, 'api': CLASSROOM,
@@ -656,7 +644,6 @@ _SVCACCT_SCOPES = [
# 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'}, # 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
# {'name': 'Cloud Identity API - Policy Beta', # {'name': 'Cloud Identity API - Policy Beta',
# 'api': CLOUDIDENTITY_POLICY_BETA, # 'api': CLOUDIDENTITY_POLICY_BETA,
# 'subscopes': [],
# 'offByDefault': True, # 'offByDefault': True,
# 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'}, # 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
# {'name': 'Cloud Identity User Invitations API', # {'name': 'Cloud Identity User Invitations API',
@@ -665,7 +652,6 @@ _SVCACCT_SCOPES = [
# 'scope': 'https://www.googleapis.com/auth/cloud-identity'}, # 'scope': 'https://www.googleapis.com/auth/cloud-identity'},
# {'name': 'Contacts API - Users', # {'name': 'Contacts API - Users',
# 'api': CONTACTS, # 'api': CONTACTS,
# 'subscopes': [],
# 'scope': 'https://www.google.com/m8/feeds'}, # 'scope': 'https://www.google.com/m8/feeds'},
{'name': 'Drive API', {'name': 'Drive API',
'api': DRIVE3, 'api': DRIVE3,
@@ -673,7 +659,6 @@ _SVCACCT_SCOPES = [
'scope': DRIVE_SCOPE}, 'scope': DRIVE_SCOPE},
{'name': 'Drive Activity API v2 - must pair with Drive API', {'name': 'Drive Activity API v2 - must pair with Drive API',
'api': DRIVEACTIVITY, 'api': DRIVEACTIVITY,
'subscopes': [],
'scope': [DRIVE_READONLY_SCOPE, 'scope': [DRIVE_READONLY_SCOPE,
'https://www.googleapis.com/auth/drive.activity']}, 'https://www.googleapis.com/auth/drive.activity']},
{'name': 'Drive Labels API - Admin', {'name': 'Drive Labels API - Admin',
@@ -690,30 +675,24 @@ _SVCACCT_SCOPES = [
'scope': 'https://www.googleapis.com/auth/documents'}, 'scope': 'https://www.googleapis.com/auth/documents'},
{'name': 'Forms API - must pair with Drive API', {'name': 'Forms API - must pair with Drive API',
'api': FORMS, 'api': FORMS,
'subscopes': [],
'scope': [DRIVE_READONLY_SCOPE, 'scope': [DRIVE_READONLY_SCOPE,
'https://www.googleapis.com/auth/forms.body', 'https://www.googleapis.com/auth/forms.body',
'https://www.googleapis.com/auth/forms.responses.readonly']}, 'https://www.googleapis.com/auth/forms.responses.readonly']},
{'name': 'Gmail API - Full Access (Labels, Messages)', {'name': 'Gmail API - Full Access (Labels, Messages)',
'api': GMAIL, 'api': GMAIL,
'subscopes': [],
'scope': 'https://mail.google.com/'}, 'scope': 'https://mail.google.com/'},
{'name': 'Gmail API - Full Access (Labels, Messages) except delete message', {'name': 'Gmail API - Full Access (Labels, Messages) except delete message',
'api': GMAIL, 'api': GMAIL,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/gmail.modify'}, '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', {'name': 'Gmail API - Basic Settings (Filters, IMAP, Language, POP, Vacation) - read/write, Sharing Settings (Delegates, Forwarding, SendAs) - read',
'api': GMAIL, 'api': GMAIL,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/gmail.settings.basic'}, 'scope': 'https://www.googleapis.com/auth/gmail.settings.basic'},
{'name': 'Gmail API - Sharing Settings (Delegates, Forwarding, SendAs) - write', {'name': 'Gmail API - Sharing Settings (Delegates, Forwarding, SendAs) - write',
'api': GMAIL, 'api': GMAIL,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/gmail.settings.sharing'}, 'scope': 'https://www.googleapis.com/auth/gmail.settings.sharing'},
# {'name': 'Identity and Access Management API', # {'name': 'Identity and Access Management API',
# 'api': IAM, # 'api': IAM,
# 'offByDefault': True, # 'offByDefault': True,
# 'subscopes': [],
# 'scope': CLOUD_PLATFORM_SCOPE}, # 'scope': CLOUD_PLATFORM_SCOPE},
{'name': 'Keep API', {'name': 'Keep API',
'api': KEEP, 'api': KEEP,
@@ -725,32 +704,26 @@ _SVCACCT_SCOPES = [
'scope': 'https://www.googleapis.com/auth/datastudio'}, 'scope': 'https://www.googleapis.com/auth/datastudio'},
{'name': 'Meet API - Manage/Display Meeting Spaces', {'name': 'Meet API - Manage/Display Meeting Spaces',
'api': MEET_SPACES, 'api': MEET_SPACES,
'subscopes': [],
'scope': ['https://www.googleapis.com/auth/meetings.space.created', 'scope': ['https://www.googleapis.com/auth/meetings.space.created',
'https://www.googleapis.com/auth/meetings.space.settings']}, '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, 'api': MEET_READONLY,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/meetings.space.readonly'}, 'scope': 'https://www.googleapis.com/auth/meetings.space.readonly'},
{'name': 'OAuth2 API', {'name': 'OAuth2 API',
'api': OAUTH2, 'api': OAUTH2,
'subscopes': [],
'scope': USERINFO_PROFILE_SCOPE}, 'scope': USERINFO_PROFILE_SCOPE},
{'name': 'People API', {'name': 'People API',
'api': PEOPLE, 'api': PEOPLE,
'subscopes': READONLY, 'subscopes': READONLY,
'scope': PEOPLE_SCOPE}, 'scope': PEOPLE_SCOPE},
{'name': 'People Directory API - read only', {'name': 'People Directory API - readonly',
'api': PEOPLE_DIRECTORY, 'api': PEOPLE_DIRECTORY,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/directory.readonly'}, 'scope': 'https://www.googleapis.com/auth/directory.readonly'},
{'name': 'People API - Other Contacts - read only', {'name': 'People API - Other Contacts - readonly',
'api': PEOPLE_OTHERCONTACTS, 'api': PEOPLE_OTHERCONTACTS,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/contacts.other.readonly'}, 'scope': 'https://www.googleapis.com/auth/contacts.other.readonly'},
{'name': 'Search Console API - read only', {'name': 'Search Console API - readonly',
'api': SEARCHCONSOLE, 'api': SEARCHCONSOLE,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': 'https://www.googleapis.com/auth/webmasters.readonly'}, 'scope': 'https://www.googleapis.com/auth/webmasters.readonly'},
{'name': 'Sheets API', {'name': 'Sheets API',
@@ -759,26 +732,22 @@ _SVCACCT_SCOPES = [
'scope': 'https://www.googleapis.com/auth/spreadsheets'}, 'scope': 'https://www.googleapis.com/auth/spreadsheets'},
{'name': 'Site Verification API', {'name': 'Site Verification API',
'api': SITEVERIFICATION, 'api': SITEVERIFICATION,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': 'https://www.googleapis.com/auth/siteverification'}, '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, 'api': TAGMANAGER,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': 'https://www.googleapis.com/auth/tagmanager.readonly'}, 'scope': 'https://www.googleapis.com/auth/tagmanager.readonly'},
{'name': 'Tag Manager API - Users', {'name': 'Tag Manager API - Users',
'api': TAGMANAGER_USERS, 'api': TAGMANAGER_USERS,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': 'https://www.googleapis.com/auth/tagmanager.manage.users'}, 'scope': 'https://www.googleapis.com/auth/tagmanager.manage.users'},
{'name': 'Tasks API', {'name': 'Tasks API',
'api': TASKS, 'api': TASKS,
'subscopes': READONLY, 'subscopes': READONLY,
'scope': 'https://www.googleapis.com/auth/tasks'}, 'scope': 'https://www.googleapis.com/auth/tasks'},
{'name': 'Youtube API - read only', {'name': 'Youtube API - readonly',
'api': YOUTUBE, 'api': YOUTUBE,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': 'https://www.googleapis.com/auth/youtube.readonly'}, 'scope': 'https://www.googleapis.com/auth/youtube.readonly'},
] ]
@@ -786,30 +755,25 @@ _SVCACCT_SCOPES = [
_SVCACCT_SPECIAL_SCOPES = [ _SVCACCT_SPECIAL_SCOPES = [
{'name': 'Drive API - write todrive data - has access to all Drive', {'name': 'Drive API - write todrive data - has access to all Drive',
'api': DRIVETD, 'api': DRIVETD,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': DRIVE_SCOPE}, 'scope': DRIVE_SCOPE},
{'name': 'Gmail API - Full Access - read only', {'name': 'Gmail API - Full Access - readonly',
'api': GMAIL, 'api': GMAIL,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': 'https://www.googleapis.com/auth/gmail.readonly'}, 'scope': 'https://www.googleapis.com/auth/gmail.readonly'},
{'name': 'Gmail API - Send Messages - including todrive', {'name': 'Gmail API - Send Messages - including todrive',
'api': GMAIL, 'api': GMAIL,
'subscopes': [],
'offByDefault': True, 'offByDefault': True,
'scope': GMAIL_SEND_SCOPE}, 'scope': GMAIL_SEND_SCOPE},
{'name': 'Sheets API - write todrive data - has access to all Sheets', {'name': 'Sheets API - write todrive data - has access to all Sheets',
'api': SHEETSTD, 'api': SHEETSTD,
'offByDefault': True, 'offByDefault': True,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/spreadsheets'}, 'scope': 'https://www.googleapis.com/auth/spreadsheets'},
] ]
_USER_SVCACCT_ONLY_SCOPES = [ _USER_SVCACCT_ONLY_SCOPES = [
{'name': 'Groups Migration API', {'name': 'Groups Migration API',
'api': GROUPSMIGRATION, 'api': GROUPSMIGRATION,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/apps.groups.migration'}, 'scope': 'https://www.googleapis.com/auth/apps.groups.migration'},
] ]
@@ -849,7 +813,7 @@ def getClientScopesURLs(commanddataClientAccess, todriveClientAccess):
def getSvcAcctScopeAPI(uscope): def getSvcAcctScopeAPI(uscope):
for scope in _SVCACCT_SCOPES: 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 scope['api']
return None return None
@@ -877,11 +841,11 @@ def findAPIforScope(scopesList):
if cscope['scope'] == scope: if cscope['scope'] == scope:
requiredAPIs.append(cscope['name']) requiredAPIs.append(cscope['name'])
return True 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)') requiredAPIs.append(cscope['name']+' (supports readonly)')
return True return True
return False return False
requiredAPIs = [] requiredAPIs = []
for scope in scopesList: for scope in scopesList:
for cscope in _CLIENT_SCOPES: for cscope in _CLIENT_SCOPES:

View File

@@ -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 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 ### 7.36.02
Added option `threadid <String>` to `gam [<UserTypeEntity>] sendemail` that causes Gmail to recognize the message Added option `threadid <String>` to `gam [<UserTypeEntity>] sendemail` that causes Gmail to recognize the message

View File

@@ -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$ rm -f /Users/gamteam/GAMConfig/oauth2.txt
gamteam@server:/Users/gamteam$ gam version 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> GAM Team <google-apps-manager@googlegroups.com>
Python 3.14.3 64-bit final Python 3.14.3 64-bit final
macOS Tahoe 26.3.1 arm64 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:\>del C:\GAMConfig\oauth2.txt
C:\>gam version 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> GAM Team <google-apps-manager@googlegroups.com>
Python 3.14.3 64-bit final Python 3.14.3 64-bit final
Windows 11 10.0.26200 AMD64 Windows 11 10.0.26200 AMD64

View File

@@ -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, 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: 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 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... Got 1 Message that matched query ((rfc822MsgId:<CAAMabc...XYZQ@mail.gmail.com>)) for recipient@domain.com...
User: recipient@domain.com, Show 1 Message User: recipient@domain.com, Show 1 Thread
Message: 19cfd414fe48430d 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 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"
```

View File

@@ -3,7 +3,7 @@
Print the current version of Gam with details Print the current version of Gam with details
``` ```
gam version 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> GAM Team <google-apps-manager@googlegroups.com>
Python 3.14.3 64-bit final Python 3.14.3 64-bit final
macOS Tahoe 26.3.1 arm64 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 Print the current version of Gam with details and time offset information
``` ```
gam version timeoffset 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> GAM Team <google-apps-manager@googlegroups.com>
Python 3.14.3 64-bit final Python 3.14.3 64-bit final
macOS Tahoe 26.3.1 arm64 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 Print the current version of Gam with extended details and SSL information
``` ```
gam version extended 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> GAM Team <google-apps-manager@googlegroups.com>
Python 3.14.3 64-bit final Python 3.14.3 64-bit final
macOS Tahoe 26.3.1 arm64 macOS Tahoe 26.3.1 arm64
@@ -68,7 +68,7 @@ MacOS High Sierra 10.13.6 x86_64
Path: /Users/gamteam/bin/gam7 Path: /Users/gamteam/bin/gam7
Version Check: Version Check:
Current: 5.35.08 Current: 5.35.08
Latest: 7.36.02 Latest: 7.36.03
echo $? echo $?
1 1
``` ```
@@ -76,7 +76,7 @@ echo $?
Print the current version number without details Print the current version number without details
``` ```
gam version simple gam version simple
7.36.02 7.36.03
``` ```
In Linux/MacOS you can do: In Linux/MacOS you can do:
``` ```
@@ -86,7 +86,7 @@ echo $VER
Print the current version of Gam and address of this Wiki Print the current version of Gam and address of this Wiki
``` ```
gam help 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> GAM Team <google-apps-manager@googlegroups.com>
Python 3.14.3 64-bit final Python 3.14.3 64-bit final
macOS Tahoe 26.3.1 arm64 macOS Tahoe 26.3.1 arm64