diff --git a/docs/Authorization.md b/docs/Authorization.md index 30245352..8944fe02 100644 --- a/docs/Authorization.md +++ b/docs/Authorization.md @@ -317,7 +317,8 @@ gam create project [admin ] [project ] [sadescription ] [(algorithm KEY_ALG_RSA_1024|KEY_ALG_RSA_2048)| (localkeysize 1024|2048|4096 [validityhours ])| - (yubikey yubikey_pin yubikey_slot AUTHENTICATION|SIGNATURE yubikey_serialnumber )] + (yubikey yubikey_pin yubikey_slot AUTHENTICATION|SIGNATURE yubikey_serialnumber )| + nokey} ``` * `admin ` - Google Workspace admin/GCP project manager; if omitted, you will be prompted for the address * `appname ` - Application name, defaults to `GAM` @@ -331,6 +332,8 @@ gam create project [admin ] [project ] You can optionally specify the type of service account key with `algorithm|localkeysize|yubikey`: [Manage Service Account keys](#manage-service-account-keys) +Use `nokey` if you do not want a service account key created for the project. + ## Use an existing project for GAM authorization Use an existing project to create and download two files: `client_secrets.json` for the Client and `oauth2service.json` for the Service Account. diff --git a/docs/GamUpdates.md b/docs/GamUpdates.md index 98c18333..09f941ee 100644 --- a/docs/GamUpdates.md +++ b/docs/GamUpdates.md @@ -10,6 +10,11 @@ Add the `-s` option to the end of the above commands to suppress creating the `g See [Downloads](https://github.com/taers232c/GAMADV-XTD3/wiki/Downloads) for Windows or other options, including manual installation +### 6.77.02 + +Cleaned up problems with some of the new Chat API asadmin commands. +Some remaining problems may require a Google fix. + ### 6.77.01 Thanks to Jay, added column `verificationCodesCount` to `gam print backupcodes` diff --git a/docs/How-to-Upgrade-from-Standard-GAM.md b/docs/How-to-Upgrade-from-Standard-GAM.md index cf2c11c3..a99af82a 100644 --- a/docs/How-to-Upgrade-from-Standard-GAM.md +++ b/docs/How-to-Upgrade-from-Standard-GAM.md @@ -335,7 +335,7 @@ writes the credentials into the file oauth2.txt. admin@server:/Users/admin/bin/gamadv-xtd3$ rm -f /Users/admin/GAMConfig/oauth2.txt admin@server:/Users/admin/bin/gamadv-xtd3$ ./gam version WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found -GAMADV-XTD3 6.77.01 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource +GAMADV-XTD3 6.77.02 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource Ross Scroggs Python 3.12.4 64-bit final MacOS Sonoma 14.5 x86_64 @@ -1009,7 +1009,7 @@ writes the credentials into the file oauth2.txt. C:\GAMADV-XTD3>del C:\GAMConfig\oauth2.txt C:\GAMADV-XTD3>gam version WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found -GAMADV-XTD3 6.77.01 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource +GAMADV-XTD3 6.77.02 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource Ross Scroggs Python 3.12.4 64-bit final Windows-10-10.0.17134 AMD64 diff --git a/docs/Users-Backup-Verification-Codes.md b/docs/Users-Backup-Verification-Codes.md index d6694b0b..c4f40e53 100644 --- a/docs/Users-Backup-Verification-Codes.md +++ b/docs/Users-Backup-Verification-Codes.md @@ -31,8 +31,10 @@ Exit Status of 0 indicates no errors, and backup codes are sent to stdout. Exit status of 60 indicates no errors, and that no backup codes are available for this user. ``` -gam print backupcodes|verificationcodes [todrive *] [delimiter ] +gam print backupcodes|verificationcodes [todrive *] + [delimiter ] [countsonly] ``` -Gam displays the information in CSV form. +GAM displays the information in CSV form. * `delimiter ` - Separate `verificationCodes` entries with ``; the default value is `csv_output_field_delimiter` from `gam.cfg`. +* `countsonly` - Display only the number of available backup codes but not the codes themselves. diff --git a/docs/Version-and-Help.md b/docs/Version-and-Help.md index 79ed3fac..1bb9ce32 100644 --- a/docs/Version-and-Help.md +++ b/docs/Version-and-Help.md @@ -3,7 +3,7 @@ Print the current version of Gam with details ``` gam version -GAMADV-XTD3 6.77.01 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource +GAMADV-XTD3 6.77.02 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource Ross Scroggs Python 3.12.4 64-bit final MacOS Sonoma 14.5 x86_64 @@ -15,7 +15,7 @@ Time: 2023-06-02T21:10:00-07:00 Print the current version of Gam with details and time offset information ``` gam version timeoffset -GAMADV-XTD3 6.77.01 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource +GAMADV-XTD3 6.77.02 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource Ross Scroggs Python 3.12.4 64-bit final MacOS Sonoma 14.5 x86_64 @@ -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 -GAMADV-XTD3 6.77.01 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource +GAMADV-XTD3 6.77.02 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource Ross Scroggs Python 3.12.4 64-bit final MacOS Sonoma 14.5 x86_64 @@ -64,7 +64,7 @@ MacOS High Sierra 10.13.6 x86_64 Path: /Users/Admin/bin/gamadv-xtd3 Version Check: Current: 5.35.08 - Latest: 6.77.01 + Latest: 6.77.02 echo $? 1 ``` @@ -72,7 +72,7 @@ echo $? Print the current version number without details ``` gam version simple -6.77.01 +6.77.02 ``` In Linux/MacOS you can do: ``` @@ -82,7 +82,7 @@ echo $VER Print the current version of Gam and address of this Wiki ``` gam help -GAM 6.77.01 - https://github.com/taers232c/GAMADV-XTD3 +GAM 6.77.02 - https://github.com/taers232c/GAMADV-XTD3 Ross Scroggs Python 3.12.4 64-bit final MacOS Sonoma 14.5 x86_64 diff --git a/src/GamUpdate.txt b/src/GamUpdate.txt index 1805d8aa..38cc078f 100644 --- a/src/GamUpdate.txt +++ b/src/GamUpdate.txt @@ -2,6 +2,11 @@ Merged GAM-Team version +6.77.02 + +Cleaned up problems with some of the new Chat API asadmin commands. +Some remaining problems may require a Google fix. + 6.77.01 Thanks to Jay, added column `verificationCodesCount` to `gam print backupcodes` diff --git a/src/gam/__init__.py b/src/gam/__init__.py index c0cc975a..0ab12f7a 100755 --- a/src/gam/__init__.py +++ b/src/gam/__init__.py @@ -25169,8 +25169,8 @@ def exitIfChatNotConfigured(chat, kvList, errMsg, i, count): def _getChatAdminAccess(adminAPI, userAPI): if checkArgumentPresent(ADMIN_ACCESS_OPTIONS) or GC.Values[GC.USE_CHAT_ADMIN_ACCESS]: - return (True, adminAPI) - return (False, userAPI) + return (True, adminAPI, {'useAdminAccess': True}) + return (False, userAPI, {}) def _chkChatAdminAccess(count): if count != 1: @@ -25395,7 +25395,7 @@ CHAT_UPDATE_SPACE_TYPE_MAP = { # [formatjson] def updateChatSpace(users): FJQC = FormatJSONQuoteChar() - useAdminAccess, api = _getChatAdminAccess(API.CHAT_SPACES_ADMIN, API.CHAT_SPACES) + useAdminAccess, api, kwargsUAA = _getChatAdminAccess(API.CHAT_SPACES_ADMIN, API.CHAT_SPACES) name = None body = {} updateMask = set() @@ -25424,8 +25424,7 @@ def updateChatSpace(users): try: space = callGAPI(chat.spaces(), 'patch', throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED], - useAdminAccess=useAdminAccess, - name=name, updateMask=','.join(updateMask), body=body) + name=name, updateMask=','.join(updateMask), body=body, **kwargsUAA) if not FJQC.formatJSON: entityActionPerformed(kvList, i, count) Ind.Increment() @@ -25438,7 +25437,7 @@ def updateChatSpace(users): # gam delete chatspace asadmin def deleteChatSpace(users): name = None - useAdminAccess, api = _getChatAdminAccess(API.CHAT_SPACES_DELETE_ADMIN, API.CHAT_SPACES_DELETE) + useAdminAccess, api, kwargsUAA = _getChatAdminAccess(API.CHAT_SPACES_DELETE_ADMIN, API.CHAT_SPACES_DELETE) while Cmd.ArgumentsRemaining(): myarg = getArgument() if myarg == 'space' or myarg.startswith('spaces/') or myarg.startswith('space/'): @@ -25457,9 +25456,8 @@ def deleteChatSpace(users): continue try: callGAPI(chat.spaces(), 'delete', - useAdminAccess=useAdminAccess, throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED], - name=name) + name=name, **kwargsUAA) entityActionPerformed(kvList, i, count) except (GAPI.notFound, GAPI.invalidArgument, GAPI.permissionDenied) as e: exitIfChatNotConfigured(chat, kvList, str(e), i, count) @@ -25494,13 +25492,12 @@ def infoChatSpace(users, name=None): FJQC = FormatJSONQuoteChar() if name is None: function = 'get' - useAdminAccess, api = _getChatAdminAccess(API.CHAT_SPACES_ADMIN, API.CHAT_SPACES) - kwargs = {'useAdminAccess': True} + useAdminAccess, api, kwargsUAA = _getChatAdminAccess(API.CHAT_SPACES_ADMIN, API.CHAT_SPACES) else: function = 'findDirectMessage' useAdminAccess = None + kwargsUAA = {} api = API.CHAT_SPACES - kwargs = {} fieldsList = [] while Cmd.ArgumentsRemaining(): myarg = getArgument() @@ -25524,7 +25521,7 @@ def infoChatSpace(users, name=None): try: space = callGAPI(chat.spaces(), function, throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED], - name=name, fields=fields, **kwargs) + name=name, fields=fields, **kwargsUAA) if not FJQC.formatJSON: entityPerformAction(kvList, i, count) Ind.Increment() @@ -25605,7 +25602,7 @@ def printShowChatSpaces(users): csvPF = CSVPrintFile(['User', 'name'] if not isinstance(users, list) else ['name']) if Act.csvFormat() else None FJQC = FormatJSONQuoteChar(csvPF) OBY = OrderBy(CHAT_SPACES_ADMIN_ORDERBY_CHOICE_MAP) - useAdminAccess, api = _getChatAdminAccess(API.CHAT_SPACES_ADMIN, API.CHAT_SPACES) + useAdminAccess, api, kwargs = _getChatAdminAccess(API.CHAT_SPACES_ADMIN, API.CHAT_SPACES) fieldsList = [] queries = [] queryTimes = {} @@ -25613,7 +25610,6 @@ def printShowChatSpaces(users): kwargs = {} if useAdminAccess: function = 'search' - kwargs['useAdminAccess'] = True queries = ['customer = "customers/my_customer" AND spaceType = "SPACE"'] else: function = 'list' @@ -25708,14 +25704,12 @@ def createChatMember(users): member = callGAPI(chat.spaces().members(), 'create', bailOnInternalError=True, throwReasons=[GAPI.ALREADY_EXISTS, GAPI.NOT_FOUND, GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR], - useAdminAccess=useAdminAccess, - parent=parent, body=body) + parent=parent, body=body, **kwargsUAA) if role != 'ROLE_MEMBER' and entityType == Ent.CHAT_MANAGER_USER: member = callGAPI(chat.spaces().members(), 'patch', bailOnInternalError=True, throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR], - useAdminAccess=useAdminAccess, - name=member['name'], updateMask='role', body={'role': role}) + name=member['name'], updateMask='role', body={'role': role}, **kwargsUAA) if not returnIdOnly: kvList[-1] = member['name'] _getChatMemberEmail(cd, member) @@ -25738,7 +25732,7 @@ def createChatMember(users): userList = [] groupList = [] returnIdOnly = False - useAdminAccess, api = _getChatAdminAccess(API.CHAT_MEMBERSHIPS_ADMIN, API.CHAT_MEMBERSHIPS) + useAdminAccess, api, kwargsUAA = _getChatAdminAccess(API.CHAT_MEMBERSHIPS_ADMIN, API.CHAT_MEMBERSHIPS) while Cmd.ArgumentsRemaining(): myarg = getArgument() if myarg == 'space' or myarg.startswith('spaces/') or myarg.startswith('space/'): @@ -25788,17 +25782,16 @@ def createChatMember(users): addMembers(groupMembers, 'groupMember', Ent.CHAT_MEMBER_GROUP, i, count) Ind.Decrement() -def _deleteChatMembers(chat, kvList, jcount, memberNames, i, count, useAdminAccess=None): +def _deleteChatMembers(chat, kvList, jcount, memberNames, i, count, kwargsUAA): j = 0 for name in memberNames: j += 1 kvList[-1] = name try: callGAPI(chat.spaces().members(), 'delete', - useAdminAccess=useAdminAccess, bailOnInternalError=True, throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR], - name=name) + name=name, **kwargsUAA) entityActionPerformed(kvList, j, jcount) except GAPI.notFound as e: entityActionFailedWarning(kvList, str(e), j, jcount) @@ -25836,7 +25829,7 @@ def deleteUpdateChatMember(users): body = {} memberNames = [] userGroupList = [] - useAdminAccess, api = _getChatAdminAccess(API.CHAT_MEMBERSHIPS_ADMIN, API.CHAT_MEMBERSHIPS) + useAdminAccess, api, kwargsUAA = _getChatAdminAccess(API.CHAT_MEMBERSHIPS_ADMIN, API.CHAT_MEMBERSHIPS) while Cmd.ArgumentsRemaining(): myarg = getArgument() if action in {Act.UPDATE, Act.MODIFY} and myarg == 'role': @@ -25887,7 +25880,7 @@ def deleteUpdateChatMember(users): kvList.extend([Ent.CHAT_MEMBER, '']) Ind.Increment() if deleteMode: - _deleteChatMembers(chat, kvList, jcount, memberNames, i, count, useAdminAccess=useAdminAccess) + _deleteChatMembers(chat, kvList, jcount, memberNames, i, count, kwargsUAA) else: j = 0 for name in memberNames: @@ -25897,8 +25890,7 @@ def deleteUpdateChatMember(users): member = callGAPI(chat.spaces().members(), 'patch', bailOnInternalError=True, throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR], - useAdminAccess=useAdminAccess, - name=name, updateMask='role', body=body) + name=name, updateMask='role', body=body, **kwargsUAA) _getChatMemberEmail(cd, member) Ind.Increment() _showChatItem(member, Ent.CHAT_MEMBER, FJQC, j, jcount) @@ -25947,14 +25939,12 @@ def syncChatMembers(users): callGAPI(chat.spaces().members(), 'create', bailOnInternalError=True, throwReasons=[GAPI.ALREADY_EXISTS, GAPI.NOT_FOUND, GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR], - useAdminAccess=useAdminAccess, - parent=parent, body=body) + parent=parent, body=body, **kwargsUAA) if role != 'ROLE_MEMBER' and entityType == Ent.CHAT_MANAGER_USER: callGAPI(chat.spaces().members(), 'patch', bailOnInternalError=True, throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR], - useAdminAccess=useAdminAccess, - name=memberName, updateMask='role', body={'role': role}) + name=memberName, updateMask='role', body={'role': role}, **kwargsUAA) entityActionPerformed(kvList, j, jcount) except (GAPI.alreadyExists, GAPI.notFound, GAPI.invalid, GAPI.invalidArgument, GAPI.permissionDenied, GAPI.internalError) as e: entityActionFailedWarning(kvList, str(e), j, jcount) @@ -25971,11 +25961,11 @@ def syncChatMembers(users): return kvList.extend([entityType, '']) Ind.Increment() - _deleteChatMembers(chat, kvList, jcount, memberNames, i, count, useAdminAccess=useAdminAccess) + _deleteChatMembers(chat, kvList, jcount, memberNames, i, count, kwargsUAA) Ind.Decrement() del kvList[-2:] - useAdminAccess, api = _getChatAdminAccess(API.CHAT_MEMBERSHIPS_ADMIN, API.CHAT_MEMBERSHIPS) + useAdminAccess, api, kwargsUAA = _getChatAdminAccess(API.CHAT_MEMBERSHIPS_ADMIN, API.CHAT_MEMBERSHIPS) cd = buildGAPIObject(API.DIRECTORY) parent = None role = CHAT_MEMBER_ROLE_MAP['member'] @@ -26045,7 +26035,7 @@ def syncChatMembers(users): members = callGAPIpages(chat.spaces().members(), 'list', 'memberships', pageMessage=_getChatPageMessage(Ent.CHAT_MEMBER, user, i, count, qfilter), throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED], - pageSize=CHAT_PAGE_SIZE, parent=parent, showGroups=groupsSpecified) + pageSize=CHAT_PAGE_SIZE, parent=parent, showGroups=groupsSpecified, **kwargsUAA) for member in members: if 'member' in member: if member['member']['type'] == mtype and member['role'] == role: @@ -26091,7 +26081,7 @@ CHAT_MEMBERS_FIELDS_CHOICE_MAP = { def infoChatMember(users): cd = buildGAPIObject(API.DIRECTORY) FJQC = FormatJSONQuoteChar() - useAdminAccess, api = _getChatAdminAccess(API.CHAT_MEMBERSHIPS_ADMIN, API.CHAT_MEMBERSHIPS) + useAdminAccess, api, kwargsUAA = _getChatAdminAccess(API.CHAT_MEMBERSHIPS_ADMIN, API.CHAT_MEMBERSHIPS) fieldsList = [] memberNames = [] while Cmd.ArgumentsRemaining(): @@ -26125,8 +26115,7 @@ def infoChatMember(users): member = callGAPI(chat.spaces().members(), 'get', bailOnInternalError=True, throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR], - useAdminAccess=useAdminAccess, - name=name, fields=fields) + name=name, fields=fields, **kwargsUAA) _getChatMemberEmail(cd, member) Ind.Increment() _showChatItem(member, Ent.CHAT_MEMBER, FJQC, j, jcount) @@ -26166,7 +26155,7 @@ def printShowChatMembers(users): csvPF = CSVPrintFile(['User', 'space.name', 'space.displayName', 'name'] if not isinstance(users, list) else ['space.name', 'space.displayName', 'name']) if Act.csvFormat() else None FJQC = FormatJSONQuoteChar(csvPF) OBY = OrderBy(CHAT_SPACES_ADMIN_ORDERBY_CHOICE_MAP) - useAdminAccess, api = _getChatAdminAccess(API.CHAT_MEMBERSHIPS_ADMIN, API.CHAT_MEMBERSHIPS) + useAdminAccess, api, kwargsUAA = _getChatAdminAccess(API.CHAT_MEMBERSHIPS_ADMIN, API.CHAT_MEMBERSHIPS) if useAdminAccess: queries = ['customer = "customers/my_customer" AND spaceType = "SPACE"'] queryTimes = {} @@ -26277,14 +26266,12 @@ def printShowChatMembers(users): if not parent['displayName']: space = callGAPI(chatsp.spaces(), 'get', throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED], - useAdminAccess=useAdminAccess, - name=parentName, fields='displayName') + name=parentName, fields='displayName', **kwargsUAA) parent['displayName'] = space.get('displayName', 'None') members = callGAPIpages(chat.spaces().members(), 'list', 'memberships', - useAdminAccess=useAdminAccess, pageMessage=_getChatPageMessage(Ent.CHAT_MEMBER, user, j, jcount, qfilter), throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED], - parent=parentName, fields=fields, pageSize=CHAT_PAGE_SIZE, **kwargs) + parent=parentName, fields=fields, pageSize=CHAT_PAGE_SIZE, **kwargs, **kwargsUAA) for member in members: _getChatMemberEmail(cd, member) except (GAPI.notFound, GAPI.invalidArgument, GAPI.permissionDenied) as e: