From 99bda1385ee23eacb13ddfededfc6eee88f8d4a2 Mon Sep 17 00:00:00 2001 From: Ross Scroggs Date: Thu, 16 Dec 2021 04:40:08 -0800 Subject: [PATCH] languages update; fields for gam info user; cloud identity groups update to v1 (#1459) * languages update The API doesn't return languages unless you specifically mention in in fields list * languages cleanup in print users * Add fields to gam info user * No up for languages * Use v1 for Cloud Identity groups; fix bug in print cigroups member * It's an error to set preference on custom language --- src/GamCommands.txt | 16 ++++-- src/gam/__init__.py | 80 ++++++++++++++++++++-------- src/gam/gapi/cloudidentity/groups.py | 38 +++++++------ 3 files changed, 92 insertions(+), 42 deletions(-) diff --git a/src/GamCommands.txt b/src/GamCommands.txt index 492f70d4..ae86dd2c 100644 --- a/src/GamCommands.txt +++ b/src/GamCommands.txt @@ -595,7 +595,7 @@ Items, separated by spaces, with spaces, commas or single quotes in the items th ::= "(,)*" ::= "(,)*" ::= "(, ::= "(, ::= "[+|-](,[+|-])*" ::= "(,)*" ::= "(,)*" ::= "(,)*" @@ -1018,7 +1018,8 @@ gam info customer gam create datatransfer|transfer ( )* gam info datatransfer|transfer -gam print datatransfers|transfers [todrive] [olduser|oldowner ] [newuser|newowner ] [status ] +gam print datatransfers|transfers [todrive] [olduser|oldowner ] [newuser|newowner ] + [status completed|failed|inprogress] gam print transferapps @@ -1470,7 +1471,11 @@ gam create user * [verifynotinvitable] gam update user * [clearschema ] [clearschema .] [verifynotinvitable] gam delete user gam undelete user [org|ou ] -gam info user [] [noaliases] [nogroups] [nolicenses|nolicences] [noschemas] [schemas|custom ] [userview] [skus|sku ] [grouptree] +gam info user [] + [quick] [noaliases] [nogroups] [nolicenses|nolicences] [noschemas] + [skus|sku ] [grouptree] + [userview] * [fields ] + [schemas|custom all|] Print fields for selected users; use domain, query/queries and deleted_only to select users to print; if none of these options are specified, all users are printed. @@ -1481,8 +1486,9 @@ gam print users [todrive] ([domain ] [(query )|(queries )] [limittoou ] [deleted_only|only_deleted]) [groups] [license|licenses|licence|licences] [emailpart|emailparts|username] - [orderby [ascending|descending]] [userview] - [allfields|basic|full | ((* | fields ) [schemas|custom all|])] + [orderby [ascending|descending]] + [userview] [allfields|basic|full | (* | fields )] + [schemas|custom all|])] [delimiter ] [sortheaders] gam create verify|verification diff --git a/src/gam/__init__.py b/src/gam/__init__.py index 3d2ec7fa..5674af69 100755 --- a/src/gam/__init__.py +++ b/src/gam/__init__.py @@ -6695,14 +6695,23 @@ def getUserAttributes(i, cd, updateCmd): for language in sys.argv[i].replace(',', ' ').split(): lang_item = {} if language[-1] == '+': + suffix = '+' language = language[:-1] lang_item['preference'] = 'preferred' elif language[-1] == '-': + suffix = '-' language = language[:-1] lang_item['preference'] = 'not_preferred' + else: + suffix = '' if language.lower() in LANGUAGE_CODES_MAP: lang_item['languageCode'] = LANGUAGE_CODES_MAP[language.lower()] else: + if suffix: + controlflow.system_error_exit( + 2, + f'suffix {suffix} not allowed with customLanguage {language}' + ) lang_item['customLanguage'] = language appendItemToBodyList(body, 'languages', lang_item) i += 1 @@ -8775,6 +8784,20 @@ def _get_admin_email(): ) return _getValueFromOAuth('email') +def _formatLanguagesList(propertyValue, delimiter): + languages = [] + for language in propertyValue: + if 'languageCode' in language: + lang = language['languageCode'] + if language.get('preference') == 'preferred': + lang += '+' + elif language.get('preference') == 'not_preferred': + lang += '-' + else: + lang = language.get('customLanguage') + languages.append(lang) + return delimiter.join(languages) + def doGetUserInfo(user_email=None): def user_lic_result(request_id, response, exception): @@ -8789,6 +8812,7 @@ def doGetUserInfo(user_email=None): i = 4 else: user_email = _get_admin_email() + fieldsList = [] getSchemas = True getAliases = True getGroups = True @@ -8819,10 +8843,35 @@ def doGetUserInfo(user_email=None): getSchemas = False projection = 'basic' i += 1 + elif myarg == 'quick': + getAliases = getCIGroups = getGroups = getLicenses = getSchemas = False + i += 1 elif myarg in ['custom', 'schemas']: getSchemas = True - projection = 'custom' - customFieldMask = sys.argv[i + 1] + if not fieldsList: + fieldsList = ['primaryEmail'] + fieldsList.append('customSchemas') + if sys.argv[i + 1].lower() == 'all': + projection = 'full' + else: + projection = 'custom' + customFieldMask = sys.argv[i + 1].replace(' ', ',') + i += 2 + elif myarg in USER_ARGUMENT_TO_PROPERTY_MAP: + if not fieldsList: + fieldsList = ['primaryEmail',] + fieldsList.extend(USER_ARGUMENT_TO_PROPERTY_MAP[myarg]) + i += 1 + elif myarg == 'fields': + if not fieldsList: + fieldsList = ['primaryEmail',] + fieldNameList = sys.argv[i + 1] + for field in fieldNameList.lower().replace(',', ' ').split(): + if field in USER_ARGUMENT_TO_PROPERTY_MAP: + fieldsList.extend(USER_ARGUMENT_TO_PROPERTY_MAP[field]) + else: + controlflow.invalid_argument_exit(field, + 'gam info users fields') i += 2 elif myarg == 'userview': viewType = 'domain_public' @@ -8836,6 +8885,7 @@ def doGetUserInfo(user_email=None): 'get', userKey=user_email, projection=projection, + fields=','.join(set(fieldsList)) if fieldsList else '*', customFieldMask=customFieldMask, viewType=viewType) print(f'User: {user["primaryEmail"]}') @@ -8844,19 +8894,7 @@ def doGetUserInfo(user_email=None): if 'name' in user and 'familyName' in user['name']: print(f'Last Name: {user["name"]["familyName"]}') if 'languages' in user: - languages = [] - for language in user['languages']: - if 'languageCode' in language: - lang = language['languageCode'] - if language.get('preference') == 'preferred': - lang += '+' - elif language.get('preference') == 'not_preferred': - lang += '-' - else: - lang = language.get('customLanguage') - languages.append(lang) - if languages: - print(f'Custom Languages: {",".join(languages)}') + print(f"Languages: {_formatLanguagesList(user['languages'], ',')}") if 'isAdmin' in user: print(f'Is a Super Admin: {user["isAdmin"]}') if 'isDelegatedAdmin' in user: @@ -9701,7 +9739,7 @@ def doPrintUsers(): projection = 'full' else: projection = 'custom' - customFieldMask = sys.argv[i + 1] + customFieldMask = sys.argv[i + 1].replace(' ', ',') i += 2 elif myarg == 'todrive': todrive = True @@ -9742,17 +9780,13 @@ def doPrintUsers(): i += 2 elif myarg in USER_ARGUMENT_TO_PROPERTY_MAP: if not fieldsList: - fieldsList = [ - 'primaryEmail', - ] + fieldsList = ['primaryEmail',] display.add_field_to_csv_file(myarg, USER_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles) i += 1 elif myarg == 'fields': if not fieldsList: - fieldsList = [ - 'primaryEmail', - ] + fieldsList = ['primaryEmail',] fieldNameList = sys.argv[i + 1] for field in fieldNameList.lower().replace(',', ' ').split(): if field in USER_ARGUMENT_TO_PROPERTY_MAP: @@ -9814,6 +9848,8 @@ def doPrintUsers(): if user_email.find('@') != -1: user['primaryEmailLocal'], user[ 'primaryEmailDomain'] = splitEmailAddress(user_email) + if 'languages' in user: + user['languages'] = _formatLanguagesList(user.pop('languages'), ' ') display.add_row_titles_to_csv_file(utils.flatten_json(user), csvRows, titles) if sortHeaders: diff --git a/src/gam/gapi/cloudidentity/groups.py b/src/gam/gapi/cloudidentity/groups.py index 2ce916e5..69ae74d3 100644 --- a/src/gam/gapi/cloudidentity/groups.py +++ b/src/gam/gapi/cloudidentity/groups.py @@ -12,6 +12,14 @@ from gam.gapi import errors as gapi_errors from gam.gapi import cloudidentity as gapi_cloudidentity from gam.gapi.directory import customer as gapi_directory_customer +# This allows easy switching between v1 and v1beta1 +# v1 +CIGROUP_API_BETA = 'cloudidentity' +CIGROUP_MEMBERKEY = 'preferredMemberKey' +# v1beta1 +#CIGROUP_API_BETA = 'cloudidentity_beta' +#CIGROUP_MEMBERKEY = 'memberKey' + def create(): ci = gapi_cloudidentity.build() @@ -73,7 +81,7 @@ def delete(): def info(): - ci = gapi_cloudidentity.build('cloudidentity_beta') + ci = gapi_cloudidentity.build(CIGROUP_API_BETA) group = gam.normalizeEmailAddressOrUID(sys.argv[3]) getUsers = True getSecuritySettings = True @@ -126,7 +134,7 @@ def info(): print(' Members:') for member in members: role = get_single_role(member.get('roles', [])).lower() - email = member.get('preferredMemberKey', {}).get('id') + email = member.get(CIGROUP_MEMBERKEY, {}).get('id') member_type = member.get('type', 'USER').lower() jc_string = '' if showJoinDate: @@ -155,7 +163,7 @@ def print_member_tree(ci, group_id, cached_group_members, spaces, show_role): for member in cached_group_members[group_id]: member_id = member.get('name', '') member_id = member_id.split('/')[-1] - email = member.get('preferredMemberKey', {}).get('id') + email = member.get(CIGROUP_MEMBERKEY, {}).get('id') member_type = member.get('type', 'USER').lower() if show_role: role = get_single_role(member.get('roles', [])).lower() @@ -197,7 +205,7 @@ GROUP_ROLES_MAP = { def print_(): - ci = gapi_cloudidentity.build('cloudidentity_beta') + ci = gapi_cloudidentity.build(CIGROUP_API_BETA) i = 3 members = False membersCountOnly = False @@ -335,12 +343,12 @@ def print_(): ) page_message = gapi.got_total_items_first_last_msg('Members') validRoles, _, _ = gam._getRoleVerification( - '.'.join(roles), 'nextPageToken,members(email,id,role)') + ','.join(roles), 'nextPageToken,members(email,id,role)') groupMembers = gapi.get_all_pages(ci.groups().memberships(), 'list', 'memberships', page_message=page_message, - message_attribute=['preferredMemberKey', 'id'], + message_attribute=[CIGROUP_MEMBERKEY, 'id'], soft_errors=True, parent=groupKey_id, view='BASIC') @@ -354,7 +362,7 @@ def print_(): ownersList = [] ownersCount = 0 for member in groupMembers: - member_email = member['preferredMemberKey']['id'] + member_email = member[CIGROUP_MEMBERKEY]['id'] role = get_single_role(member.get('roles', [])) if not validRoles or role in validRoles: if role == ROLE_MEMBER: @@ -447,7 +455,7 @@ def _get_groups_list(ci=None, member=None, parent=None): def get_membership_graph(member): - ci = gapi_cloudidentity.build('cloudidentity_beta') + ci = gapi_cloudidentity.build(CIGROUP_API_BETA) query = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels" result = gapi.call(ci.groups().memberships(), 'getMembershipGraph', @@ -457,7 +465,7 @@ def get_membership_graph(member): def print_members(): - ci = gapi_cloudidentity.build('cloudidentity_beta') + ci = gapi_cloudidentity.build(CIGROUP_API_BETA) todrive = False gapi_directory_customer.setTrueCustomerId() parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}' @@ -514,8 +522,8 @@ def print_members(): view='FULL', pageSize=500, page_message=page_message, - message_attribute=['preferredMemberKey', 'id']) - #fields='nextPageToken,memberships(preferredMemberKey,roles,createTime,updateTime)') + message_attribute=[CIGROUP_MEMBERKEY, 'id']) + #fields=f'nextPageToken,memberships({CIGROUP_MEMBERKEY},roles,createTime,updateTime)') if roles: group_members = filter_members_to_roles(group_members, roles) for member in group_members: @@ -573,7 +581,7 @@ def update(): ] return (role, expireTime, users_email) - ci = gapi_cloudidentity.build('cloudidentity_beta') + ci = gapi_cloudidentity.build(CIGROUP_API_BETA) group = sys.argv[3] myarg = sys.argv[4].lower() items = [] @@ -600,7 +608,7 @@ def update(): items.append(item) elif len(users_email) > 0: body = { - 'preferredMemberKey': { + CIGROUP_MEMBERKEY: { 'id': users_email[0] }, 'roles': [{ @@ -820,12 +828,12 @@ def update(): page_message=page_message, throw_reasons=gapi_errors.MEMBERS_THROW_REASONS, parent=parent, - fields='nextPageToken,memberships(preferredMemberKey,roles)') + fields=f'nextPageToken,memberships({CIGROUP_MEMBERKEY},roles)') result = filter_members_to_roles(result, roles) if not result: print('Group already has 0 members') return - users_email = [member['preferredMemberKey']['id'] for member in result] + users_email = [member[CIGROUP_MEMBERKEY]['id'] for member in result] sys.stderr.write( f'Group: {group}, Will remove {len(users_email)} {", ".join(roles).lower()}s.\n' )