def _getRoleAndUsers(): checkSuspended = None role = ROLE_MEMBER expireTime = None i = 5 if sys.argv[i].lower() in GROUP_ROLES_MAP: role = GROUP_ROLES_MAP[sys.argv[i].lower()] i += 1 if sys.argv[i].lower() in ['suspended', 'notsuspended']: checkSuspended = sys.argv[i].lower() == 'suspended' i += 1 if sys.argv[i].lower() in ['expire', 'expires']: if role != ROLE_MEMBER: controlflow.invalid_argument_exit(sys.argv[i], f'role {role}') expireTime = utils.get_time_or_delta_from_now(sys.argv[i + 1]) i += 2 if sys.argv[i].lower() in usergroup_types: users_email = gam.getUsersToModify(entity_type=sys.argv[i].lower(), entity=sys.argv[i + 1], checkSuspended=checkSuspended, groupUserMembersOnly=False) else: users_email = [ gam.normalizeEmailAddressOrUID(sys.argv[i], checkForCustomerId=True) ] return (role, expireTime, users_email)
def _getRoleAndUsers(): checkSuspended = None role = None expireTime = None i = 5 if sys.argv[i].lower() in GROUP_ROLES_MAP: role = GROUP_ROLES_MAP[sys.argv[i].lower()] i += 1 if sys.argv[i].lower() in ['suspended', 'notsuspended']: checkSuspended = sys.argv[i].lower() == 'suspended' i += 1 if sys.argv[i].lower() in ['expire', 'expires']: expireTime = sys.argv[i + 1] i += 2 if sys.argv[i].lower() in usergroup_types: users_email = gam.getUsersToModify(entity_type=sys.argv[i].lower(), entity=sys.argv[i + 1], checkSuspended=checkSuspended, groupUserMembersOnly=False) else: users_email = [ gam.normalizeEmailAddressOrUID(sys.argv[i], checkForCustomerId=True) ] return (role, expireTime, users_email)
def wait_for_mailbox(users): '''Wait until users mailbox is provisioned.''' cd = gapi_directory.build() i = 0 count = len(users) for user in users: i += 1 user = gam.normalizeEmailAddressOrUID(user) while True: try: result = gapi.call( cd.users(), 'get', 'fields=isMailboxSetup', userKey=user, throw_reasons=[gapi_errors.ErrorReason.USER_NOT_FOUND]) except gapi_errors.GapiUserNotFoundError: print( f'{user} mailboxIsSetup: False (user does not exist yet)') sleep(3) continue mailbox_is_setup = result.get('isMailboxSetup') print(f'{user} mailboxIsSetup: {mailbox_is_setup}') if mailbox_is_setup: break sleep(3)
def info(): ci = gapi_cloudidentity.build('cloudidentity_beta') group = gam.normalizeEmailAddressOrUID(sys.argv[3]) getUsers = True showJoinDate = True showUpdateDate = False showMemberTree = False i = 4 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'nousers': getUsers = False i += 1 elif myarg == 'nojoindate': showJoinDate = False i += 1 elif myarg == 'showupdatedate': showUpdateDate = True i += 1 elif myarg == 'membertree': showMemberTree = True i += 1 else: controlflow.invalid_argument_exit(myarg, 'gam info cigroup') name = group_email_to_id(ci, group) basic_info = gapi.call(ci.groups(), 'get', name=name) display.print_json(basic_info) if getUsers and not showMemberTree: if not showJoinDate and not showUpdateDate: view = 'BASIC' pageSize = 1000 else: view = 'FULL' pageSize = 500 members = gapi.get_all_pages(ci.groups().memberships(), 'list', 'memberships', parent=name, fields='*', pageSize=pageSize, view=view) print(' Members:') for member in members: role = get_single_role(member.get('roles', [])).lower() email = member.get('memberKey', {}).get('id') member_type = member.get('type', 'USER').lower() jc_string = '' if showJoinDate: joined = member.get('createTime', 'Unknown') jc_string += f' joined {joined}' if showUpdateDate: updated = member.get('updateTime', 'Unknown') jc_string += f' updated {updated}' print(f' {role}: {email} ({member_type}){jc_string}') print(f'Total {len(members)} users in group') elif showMemberTree: print(' Membership Tree:') cached_group_members = {} print_member_tree(ci, name, cached_group_members, 2, True)
def info_member(): ci = gapi_cloudidentity.build() member = gam.normalizeEmailAddressOrUID(sys.argv[3]) group = gam.normalizeEmailAddressOrUID(sys.argv[4]) group_name = gapi.call(ci.groups(), 'lookup', groupKey_id=group, fields='name').get('name') member_name = gapi.call(ci.groups().memberships(), 'lookup', parent=group_name, memberKey_id=member, fields='name').get('name') member_details = gapi.call(ci.groups().memberships(), 'get', name=member_name) display.print_json(member_details)
def signout(users): cd = gapi_directory.build() i = 0 count = len(users) for user in users: i += 1 user = gam.normalizeEmailAddressOrUID(user) print(f'Signing Out {user}{gam.currentCount(i, count)}') gapi.call(cd.users(), 'signOut', soft_errors=True, userKey=user)
def _getCalendarACLScope(i, body): body['scope'] = {} myarg = sys.argv[i].lower() body['scope']['type'] = myarg i += 1 if myarg in ['user', 'group']: body['scope']['value'] = gam.normalizeEmailAddressOrUID(sys.argv[i], noUid=True) i += 1 elif myarg == 'domain': if i < len(sys.argv) and \ sys.argv[i].lower().replace('_', '') != 'sendnotifications': body['scope']['value'] = sys.argv[i].lower() i += 1 else: body['scope']['value'] = GC_Values[GC_DOMAIN] elif myarg != 'default': body['scope']['type'] = 'user' body['scope']['value'] = gam.normalizeEmailAddressOrUID(myarg, noUid=True) return i
def delete(): cd = gapi_directory.build() user_email = gam.normalizeEmailAddressOrUID(sys.argv[3]) print(f'Deleting account for {user_email}') try: gapi.call(cd.users(), 'delete', userKey=user_email, throw_reasons=[gapi_errors.ErrorReason.CONDITION_NOT_MET]) except gam.gapi.errors.GapiConditionNotMetError as err: display.print_error( f'{err} The user {user_email} may be (or have recently been) on Google Vault Hold and thus not eligible for deletion. You can check holds with "gam user <email> show vaultholds".' )
def create(): ci = gapi_cloudidentity.build('cloudidentity_beta') initialGroupConfig = 'EMPTY' gapi_directory_customer.setTrueCustomerId() parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}' body = { 'groupKey': { 'id': gam.normalizeEmailAddressOrUID(sys.argv[3], noUid=True) }, 'parent': parent, 'labels': { 'cloudidentity.googleapis.com/groups.discussion_forum': '' }, } i = 4 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'name': body['displayName'] = sys.argv[i + 1] i += 2 elif myarg == 'description': body['description'] = sys.argv[i + 1] i += 2 elif myarg in ['alias', 'aliases']: # As of 2020/06/25 this doesn't work (yet?) aliases = sys.argv[i + 1].split(' ') body['additionalGroupKeys'] = [] for alias in aliases: body['additionalGroupKeys'].append({'id': alias}) i += 2 elif myarg in ['dynamic']: # As of 2020/06/25 this doesn't work (yet?) body['dynamicGroupMetadata'] = { 'queries': [{ 'query': sys.argv[i + 1], 'resourceType': 'USER' }] } i += 2 elif myarg in ['makeowner']: initialGroupConfig = 'WITH_INITIAL_OWNER' i += 1 else: print('should not get here') sys.exit(5) print(f'Creating group {body["groupKey"]["id"]}') gapi.call(ci.groups(), 'create', initialGroupConfig=initialGroupConfig, body=body)
def turn_off_2sv(users): cd = gapi_directory.build() i = 0 count = len(users) for user in users: i += 1 user = gam.normalizeEmailAddressOrUID(user) print( f'Turning Off 2-Step Verification for {user}{gam.currentCount(i, count)}' ) gapi.call(cd.twoStepVerification(), 'turnOff', soft_errors=True, userKey=user)
def group_email_to_id(ci, group, i=0, count=0): group = gam.normalizeEmailAddressOrUID(group) try: return gapi.call(ci.groups(), 'lookup', throw_reasons=gapi_errors.GROUP_GET_THROW_REASONS, retry_reasons=gapi_errors.GROUP_GET_RETRY_REASONS, groupKey_id=group, fields='name').get('name') except (gapi_errors.GapiGroupNotFoundError, gapi_errors.GapiDomainNotFoundError, gapi_errors.GapiDomainCannotUseApisError, gapi_errors.GapiForbiddenError, gapi_errors.GapiBadRequestError): entityUnknownWarning('Group', group, i, count) return None
def wait_for_mailbox(users): '''Wait until users mailbox is provisioned.''' cd = gapi_directory.build() i = 0 count = len(users) for user in users: i += 1 user = gam.normalizeEmailAddressOrUID(user) while True: result = gapi.call(cd.users(), 'get', 'fields=isMailboxSetup', userKey=user) mailbox_is_setup = result.get('isMailboxSetup') print(f'{user} mailboxIsSetup: {mailbox_is_setup}') if mailbox_is_setup: break sleep(3)
def delete(users): condel = build() delegate = gam.normalizeEmailAddressOrUID(sys.argv[5]) delegate = gapi_directory_users.get_primary(delegate) if not delegate: controlflow.system_error_exit( 5, f'{sys.argv[5]} is not the primary address of a user.') i = 0 count = len(users) for user in users: i += 1 print( f'Deleting {delegate} contact delegate access to {user}{gam.currentCount(i, count)}' ) gapi.call(condel.delegates(), 'delete', soft_errors=True, user=user, delegate=delegate)
def create(): cd = gapi_directory.build() user = gam.normalizeEmailAddressOrUID(sys.argv[3]) body = {'assignedTo': gam.convertEmailAddressToUID(user, cd)} role = sys.argv[4] body['roleId'] = gapi_directory_roles.getRoleId(role) body['scopeType'] = sys.argv[5].upper() i = 6 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == 'condition': cd = gapi_directory.build_beta() body['condition'] = sys.argv[i + 1] if body['condition'] == 'securitygroup': body['condition'] = SECURITY_GROUP_CONDITION elif body['condition'] == 'nonsecuritygroup': body['condition'] = NONSECURITY_GROUP_CONDITION i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam create admin') if body['scopeType'] not in ['CUSTOMER', 'ORG_UNIT']: controlflow.expected_argument_exit('scope type', ', '.join(['customer', 'org_unit']), body['scopeType']) if body['scopeType'] == 'ORG_UNIT': orgUnit, orgUnitId = gapi_directory_orgunits.getOrgUnitId( sys.argv[6], cd) body['orgUnitId'] = orgUnitId[3:] scope = f'ORG_UNIT {orgUnit}' else: scope = 'CUSTOMER' print(f'Giving {user} admin role {role} for {scope}') gapi.call(cd.roleAssignments(), 'insert', customer=GC_Values[GC_CUSTOMER_ID], body=body)
def print_members(): ci = gapi_cloudidentity.build() todrive = False gapi_directory_customer.setTrueCustomerId() parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}' roles = [] titles = ['group'] csvRows = [] groups_to_get = [] i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'todrive': todrive = True i += 1 elif myarg in ['role', 'roles']: for role in sys.argv[i + 1].lower().replace(',', ' ').split(): if role in GROUP_ROLES_MAP: roles.append(GROUP_ROLES_MAP[role]) else: controlflow.system_error_exit( 2, f'{role} is not a valid role for "gam print group-members {myarg}"' ) i += 2 elif myarg in ['cigroup', 'cigroups']: group_email = gam.normalizeEmailAddressOrUID(sys.argv[i + 1]) groups_to_get = [group_email] i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam print cigroup-members') if not groups_to_get: gam.printGettingAllItems('Groups', None) page_message = gapi.got_total_items_first_last_msg('Groups') groups_to_get = gapi.get_all_pages( ci.groups(), 'list', 'groups', message_attribute=['groupKey', 'id'], page_message=page_message, parent=parent, view='BASIC', pageSize=1000, fields='nextPageToken,groups(groupKey(id))') groups_to_get = [group['groupKey']['id'] for group in groups_to_get] i = 0 count = len(groups_to_get) for group_email in groups_to_get: i += 1 sys.stderr.write( f'Getting members for {group_email}{gam.currentCountNL(i, count)}') group_id = group_email_to_id(ci, group_email) print(f'Getting members of cigroup {group_email}...') page_message = f' {gapi.got_total_items_first_last_msg("Members")}' group_members = gapi.get_all_pages( ci.groups().memberships(), 'list', 'memberships', soft_errors=True, parent=group_id, view='FULL', pageSize=500, page_message=page_message, message_attribute=['memberKey', 'id']) #fields='nextPageToken,memberships(memberKey,roles,createTime,updateTime)') if roles: group_members = filter_members_to_roles(group_members, roles) for member in group_members: # reduce role to a single value member['role'] = get_single_role(member.pop('roles')) member = utils.flatten_json(member) for title in member: if title not in titles: titles.append(title) member['group'] = group_email csvRows.append(member) display.write_csv_file(csvRows, titles, 'Group Members', todrive)
def update(): # Convert [email protected] to [email protected]; eliminate periods in name for [email protected] def _cleanConsumerAddress(emailAddress, mapCleanToOriginal): atLoc = emailAddress.find('@') if atLoc > 0: if emailAddress[atLoc + 1:] in ['gmail.com', 'googlemail.com']: cleanEmailAddress = emailAddress[:atLoc].replace( '.', '') + '@gmail.com' if cleanEmailAddress != emailAddress: mapCleanToOriginal[cleanEmailAddress] = emailAddress return cleanEmailAddress return emailAddress def _getRoleAndUsers(): checkSuspended = None role = None i = 5 if sys.argv[i].lower() in GROUP_ROLES_MAP: role = GROUP_ROLES_MAP[sys.argv[i].lower()] i += 1 if sys.argv[i].lower() in ['suspended', 'notsuspended']: checkSuspended = sys.argv[i].lower() == 'suspended' i += 1 if sys.argv[i].lower() in usergroup_types: users_email = gam.getUsersToModify(entity_type=sys.argv[i].lower(), entity=sys.argv[i + 1], checkSuspended=checkSuspended, groupUserMembersOnly=False) else: users_email = [ gam.normalizeEmailAddressOrUID(sys.argv[i], checkForCustomerId=True) ] return (role, users_email) ci = gapi_cloudidentity.build() group = sys.argv[3] myarg = sys.argv[4].lower() items = [] if myarg in UPDATE_GROUP_SUBCMDS: group = gam.normalizeEmailAddressOrUID(group) if group.startswith('groups/'): parent = group else: parent = group_email_to_id(ci, group) if not parent: return if myarg == 'add': role, users_email = _getRoleAndUsers() if not role: role = ROLE_MEMBER if len(users_email) > 1: sys.stderr.write( f'Group: {group}, Will add {len(users_email)} {role}s.\n') for user_email in users_email: item = [ 'gam', 'update', 'cigroup', f'id:{parent}', 'add', role, user_email ] items.append(item) elif len(users_email) > 0: body = { 'memberKey': { 'id': users_email[0] }, 'roles': [{ 'name': ROLE_MEMBER }] } if role != ROLE_MEMBER: body['roles'].append({'name': role}) add_text = [f'as {role}'] for i in range(2): try: gapi.call( ci.groups().memberships(), 'create', throw_reasons=[ gapi_errors.ErrorReason.FOUR_O_NINE, gapi_errors.ErrorReason.MEMBER_NOT_FOUND, gapi_errors.ErrorReason.RESOURCE_NOT_FOUND, gapi_errors.ErrorReason.INVALID_MEMBER, gapi_errors.ErrorReason. CYCLIC_MEMBERSHIPS_NOT_ALLOWED ], parent=parent, body=body) print( f' Group: {group}, {users_email[0]} Added {" ".join(add_text)}' ) break except (gapi_errors.GapiMemberNotFoundError, gapi_errors.GapiResourceNotFoundError, gapi_errors.GapiInvalidMemberError, gapi_errors.GapiCyclicMembershipsNotAllowedError ) as e: print( f' Group: {group}, {users_email[0]} Add {" ".join(add_text)} Failed: {str(e)}' ) break elif myarg == 'sync': syncMembersSet = set() syncMembersMap = {} role, users_email = _getRoleAndUsers() for user_email in users_email: if user_email in ('*', GC_Values[GC_CUSTOMER_ID]): syncMembersSet.add(GC_Values[GC_CUSTOMER_ID]) else: syncMembersSet.add( _cleanConsumerAddress(user_email.lower(), syncMembersMap)) currentMembersSet = set() currentMembersMap = {} for current_email in gam.getUsersToModify( entity_type='cigroup', entity=group, member_type=role, groupUserMembersOnly=False): if current_email == GC_Values[GC_CUSTOMER_ID]: currentMembersSet.add(current_email) else: currentMembersSet.add( _cleanConsumerAddress(current_email.lower(), currentMembersMap)) to_add = [ syncMembersMap.get(emailAddress, emailAddress) for emailAddress in syncMembersSet - currentMembersSet ] to_remove = [ currentMembersMap.get(emailAddress, emailAddress) for emailAddress in currentMembersSet - syncMembersSet ] sys.stderr.write( f'Group: {group}, Will add {len(to_add)} and remove {len(to_remove)} {role}s.\n' ) for user in to_add: item = [ 'gam', 'update', 'cigroup', f'id:{parent}', 'add', role, user ] items.append(item) for user in to_remove: items.append([ 'gam', 'update', 'cigroup', f'id:{parent}', 'remove', user ]) elif myarg in ['delete', 'remove']: _, users_email = _getRoleAndUsers() if len(users_email) > 1: sys.stderr.write( f'Group: {group}, Will remove {len(users_email)} emails.\n' ) for user_email in users_email: items.append([ 'gam', 'update', 'cigroup', f'id:{parent}', 'remove', user_email ]) elif len(users_email) == 1: name = membership_email_to_id(ci, parent, users_email[0]) try: gapi.call(ci.groups().memberships(), 'delete', throw_reasons=[ gapi_errors.ErrorReason.MEMBER_NOT_FOUND, gapi_errors.ErrorReason.INVALID_MEMBER ], name=name) print(f' Group: {group}, {users_email[0]} Removed') except (gapi_errors.GapiMemberNotFoundError, gapi_errors.GapiInvalidMemberError) as e: print( f' Group: {group}, {users_email[0]} Remove Failed: {str(e)}' ) elif myarg == 'update': role, users_email = _getRoleAndUsers() if not role: role = ROLE_MEMBER if len(users_email) > 1: sys.stderr.write( f'Group: {group}, Will update {len(users_email)} {role}s.\n' ) for user_email in users_email: item = [ 'gam', 'update', 'cigroup', f'id:{parent}', 'update', role, user_email ] items.append(item) elif len(users_email) > 0: name = membership_email_to_id(ci, parent, users_email[0]) addRoles = [] removeRoles = [] new_role = {'role': role} current_roles = gapi.call(ci.groups().memberships(), 'get', name=name, fields='roles').get('roles', []) current_roles = [role['name'] for role in current_roles] for crole in current_roles: if crole != ROLE_MEMBER and crole != role: removeRoles.append(crole) if role not in current_roles: addRoles.append({'name': role}) bodys = [] if addRoles: bodys.append({'addRoles': addRoles}) if removeRoles: bodys.append({'removeRoles': removeRoles}) for body in bodys: try: gapi.call(ci.groups().memberships(), 'modifyMembershipRoles', throw_reasons=[ gapi_errors.ErrorReason.MEMBER_NOT_FOUND, gapi_errors.ErrorReason.INVALID_MEMBER ], name=name, body=body) except (gapi_errors.GapiMemberNotFoundError, gapi_errors.GapiInvalidMemberError) as e: print( f' Group: {group}, {users_email[0]} Update to {role} Failed: {str(e)}' ) break print(f' Group: {group}, {users_email[0]} Updated to {role}') else: # clear roles = [] i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg.upper() in [ROLE_OWNER, ROLE_MANAGER, ROLE_MEMBER]: roles.append(myarg.upper()) i += 1 else: controlflow.invalid_argument_exit( sys.argv[i], 'gam update cigroup clear') if not roles: roles = [ROLE_MEMBER] group = gam.normalizeEmailAddressOrUID(group) member_type_message = f'{",".join(roles).lower()}s' sys.stderr.write( f'Getting {member_type_message} of {group} (may take some time for large groups)...\n' ) page_message = gapi.got_total_items_msg(f'{member_type_message}', '...') try: result = gapi.get_all_pages( ci.groups().memberships(), 'list', 'memberships', page_message=page_message, throw_reasons=gapi_errors.MEMBERS_THROW_REASONS, parent=parent, fields='nextPageToken,memberships(memberKey,roles)') result = filter_members_to_roles(result, roles) if not result: print('Group already has 0 members') return users_email = [member['memberKey']['id'] for member in result] sys.stderr.write( f'Group: {group}, Will remove {len(users_email)} {", ".join(roles).lower()}s.\n' ) for user_email in users_email: items.append([ 'gam', 'update', 'cigroup', group, 'remove', user_email ]) except (gapi_errors.GapiGroupNotFoundError, gapi_errors.GapiDomainNotFoundError, gapi_errors.GapiInvalidError, gapi_errors.GapiForbiddenError): gam.entityUnknownWarning('Group', group, 0, 0) if items: gam.run_batch(items) else: i = 4 body = {} while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'name': body['displayName'] = sys.argv[i + 1] i += 2 elif myarg == 'description': body['description'] = sys.argv[i + 1] i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam update cigroup') updateMask = ','.join(body.keys()) name = group_email_to_id(ci, group) print(f'Updating group {group}') gapi.call(ci.groups(), 'patch', updateMask=updateMask, name=name, body=body)
def print_members(): ci = gapi_cloudidentity.build(CIGROUP_API_BETA) todrive = False gapi_directory_customer.setTrueCustomerId() parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}' usemember = None roles = [] titles = ['group'] csvRows = [] groups_to_get = [] i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'todrive': todrive = True i += 1 elif myarg in ['role', 'roles']: for role in sys.argv[i + 1].lower().replace(',', ' ').split(): if role in GROUP_ROLES_MAP: roles.append(GROUP_ROLES_MAP[role]) else: controlflow.system_error_exit( 2, f'{role} is not a valid role for "gam print group-members {myarg}"' ) i += 2 elif myarg == 'enterprisemember': member = gam.convertUIDtoEmailAddress( sys.argv[i + 1], email_types=['user', 'group']) usemember = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels" i += 2 elif myarg in ['cigroup', 'cigroups']: group_email = gam.normalizeEmailAddressOrUID(sys.argv[i + 1]) groups_to_get = [group_email] i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam print cigroup-members') if not groups_to_get: groups_to_get = _get_groups_list(ci, usemember, parent) i = 0 count = len(groups_to_get) for group_email in groups_to_get: i += 1 sys.stderr.write( f'Getting members for {group_email}{gam.currentCountNL(i, count)}') group_id = group_email_to_id(ci, group_email) print(f'Getting members of cigroup {group_email}...') page_message = f' {gapi.got_total_items_first_last_msg("Members")}' group_members = gapi.get_all_pages( ci.groups().memberships(), 'list', 'memberships', soft_errors=True, parent=group_id, view='FULL', pageSize=500, page_message=page_message, 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: # reduce role to a single value member['role'] = get_single_role(member.pop('roles')) member = utils.flatten_json(member) for title in member: if title not in titles: titles.append(title) member['group'] = group_email csvRows.append(member) display.write_csv_file(csvRows, titles, 'Group Members', todrive)
def update(): # Convert [email protected] to [email protected]; eliminate periods in name for [email protected] def _cleanConsumerAddress(emailAddress, mapCleanToOriginal): atLoc = emailAddress.find('@') if atLoc > 0: if emailAddress[atLoc + 1:] in ['gmail.com', 'googlemail.com']: cleanEmailAddress = emailAddress[:atLoc].replace( '.', '') + '@gmail.com' if cleanEmailAddress != emailAddress: mapCleanToOriginal[cleanEmailAddress] = emailAddress return cleanEmailAddress return emailAddress def _getRoleAndUsers(): checkSuspended = None role = ROLE_MEMBER expireTime = None i = 5 if sys.argv[i].lower() in GROUP_ROLES_MAP: role = GROUP_ROLES_MAP[sys.argv[i].lower()] i += 1 if sys.argv[i].lower() in ['suspended', 'notsuspended']: checkSuspended = sys.argv[i].lower() == 'suspended' i += 1 if sys.argv[i].lower() in ['expire', 'expires']: if role != ROLE_MEMBER: controlflow.invalid_argument_exit( sys.argv[i], f'role {role}') expireTime = utils.get_time_or_delta_from_now(sys.argv[i+1]) i += 2 if sys.argv[i].lower() in usergroup_types: users_email = gam.getUsersToModify(entity_type=sys.argv[i].lower(), entity=sys.argv[i + 1], checkSuspended=checkSuspended, groupUserMembersOnly=False) else: users_email = [ gam.normalizeEmailAddressOrUID(sys.argv[i], checkForCustomerId=True) ] return (role, expireTime, users_email) ci = gapi_cloudidentity.build('cloudidentity_beta') group = sys.argv[3] myarg = sys.argv[4].lower() items = [] if myarg in UPDATE_GROUP_SUBCMDS: group = gam.normalizeEmailAddressOrUID(group) if group.startswith('groups/'): parent = group else: parent = group_email_to_id(ci, group) if not parent: return if myarg == 'add': role, expireTime, users_email = _getRoleAndUsers() if len(users_email) > 1: sys.stderr.write( f'Group: {group}, Will add {len(users_email)} {role}s.\n') for user_email in users_email: item = [ 'gam', 'update', 'cigroup', f'id:{parent}', 'add', role, ] if expireTime: item.extend(['expires', expireTime]) item.append(user_email) items.append(item) elif len(users_email) > 0: body = { 'preferredMemberKey': { 'id': users_email[0] }, 'roles': [{ 'name': ROLE_MEMBER }] } if role != ROLE_MEMBER: body['roles'].append({'name': role}) elif expireTime not in {None, NEVER_TIME}: for role in body['roles']: if role['name'] == ROLE_MEMBER: role['expiryDetail'] = {'expireTime': expireTime} add_text = [f'as {role}'] for i in range(2): try: gapi.call( ci.groups().memberships(), 'create', throw_reasons=[ gapi_errors.ErrorReason.FOUR_O_NINE, gapi_errors.ErrorReason.MEMBER_NOT_FOUND, gapi_errors.ErrorReason.RESOURCE_NOT_FOUND, gapi_errors.ErrorReason.INVALID_MEMBER, gapi_errors.ErrorReason. CYCLIC_MEMBERSHIPS_NOT_ALLOWED ], parent=parent, body=body) print( f' Group: {group}, {users_email[0]} Added {" ".join(add_text)}' ) break except (gapi_errors.GapiMemberNotFoundError, gapi_errors.GapiResourceNotFoundError, gapi_errors.GapiInvalidMemberError, gapi_errors.GapiCyclicMembershipsNotAllowedError ) as e: print( f' Group: {group}, {users_email[0]} Add {" ".join(add_text)} Failed: {str(e)}' ) break elif myarg == 'sync': syncMembersSet = set() syncMembersMap = {} role, expireTime, users_email = _getRoleAndUsers() for user_email in users_email: if user_email in ('*', GC_Values[GC_CUSTOMER_ID]): syncMembersSet.add(GC_Values[GC_CUSTOMER_ID]) else: syncMembersSet.add( _cleanConsumerAddress(user_email.lower(), syncMembersMap)) currentMembersSet = set() currentMembersMap = {} for current_email in gam.getUsersToModify( entity_type='cigroup', entity=group, member_type=role, groupUserMembersOnly=False): if current_email == GC_Values[GC_CUSTOMER_ID]: currentMembersSet.add(current_email) else: currentMembersSet.add( _cleanConsumerAddress(current_email.lower(), currentMembersMap)) to_add = [ syncMembersMap.get(emailAddress, emailAddress) for emailAddress in syncMembersSet - currentMembersSet ] to_remove = [ currentMembersMap.get(emailAddress, emailAddress) for emailAddress in currentMembersSet - syncMembersSet ] sys.stderr.write( f'Group: {group}, Will add {len(to_add)} and remove {len(to_remove)} {role}s.\n' ) for user in to_add: item = ['gam', 'update', 'cigroup', f'id:{parent}', 'add', role,] if role == ROLE_MEMBER and expireTime not in {None, NEVER_TIME}: item.extend(['expires', expireTime]) item.append(user) items.append(item) for user in to_remove: items.append([ 'gam', 'update', 'cigroup', f'id:{parent}', 'remove', user ]) elif myarg in ['delete', 'remove']: _, _, users_email = _getRoleAndUsers() if len(users_email) > 1: sys.stderr.write( f'Group: {group}, Will remove {len(users_email)} emails.\n') for user_email in users_email: items.append([ 'gam', 'update', 'cigroup', f'id:{parent}', 'remove', user_email ]) elif len(users_email) == 1: name = membership_email_to_id(ci, parent, users_email[0]) try: gapi.call(ci.groups().memberships(), 'delete', throw_reasons=[ gapi_errors.ErrorReason.MEMBER_NOT_FOUND, gapi_errors.ErrorReason.INVALID_MEMBER ], name=name) print(f' Group: {group}, {users_email[0]} Removed') except (gapi_errors.GapiMemberNotFoundError, gapi_errors.GapiInvalidMemberError) as e: print( f' Group: {group}, {users_email[0]} Remove Failed: {str(e)}' ) elif myarg == 'update': role, expireTime, users_email = _getRoleAndUsers() if len(users_email) > 1: sys.stderr.write( f'Group: {group}, Will update {len(users_email)} {role}s.\n' ) for user_email in users_email: item = [ 'gam', 'update', 'cigroup', f'id:{parent}', 'update', role,] if expireTime: item.extend(['expires', expireTime]) item.append(user_email) items.append(item) elif len(users_email) > 0: name = membership_email_to_id(ci, parent, users_email[0]) preUpdateRoles = [] addRoles = [] removeRoles = [] postUpdateRoles = [] member_roles = gapi.call(ci.groups().memberships(), 'get', name=name, fields='roles').get('roles', [{'name': ROLE_MEMBER}]) current_roles = [crole['name'] for crole in member_roles] # When upgrading role, strip any expiryDetail from member before role changes if role != ROLE_MEMBER: for crole in member_roles: if 'expiryDetail' in crole: preUpdateRoles.append( {'fieldMask': 'expiryDetail.expireTime', 'membershipRole': {'name': ROLE_MEMBER, 'expiryDetail': {'expireTime': None}}}) break # When downgrading role or simply updating member expireTime, update expiryDetail after role changes elif expireTime: postUpdateRoles.append( {'fieldMask': 'expiryDetail.expireTime', 'membershipRole': {'name': role, 'expiryDetail': {'expireTime': expireTime if expireTime != NEVER_TIME else None}}}) for crole in current_roles: if crole not in {ROLE_MEMBER, role}: removeRoles.append(crole) if role not in current_roles: new_role = {'name': role} if role == ROLE_MEMBER and expireTime not in {None, NEVER_TIME}: new_role['expiryDetail'] = {'expireTime': expireTime} postUpdateRoles = [] addRoles.append(new_role) bodys = [] if preUpdateRoles: bodys.append({'updateRolesParams': preUpdateRoles}) if addRoles: bodys.append({'addRoles': addRoles}) if removeRoles: bodys.append({'removeRoles': removeRoles}) if postUpdateRoles: bodys.append({'updateRolesParams': postUpdateRoles}) for body in bodys: try: gapi.call(ci.groups().memberships(), 'modifyMembershipRoles', throw_reasons=[ gapi_errors.ErrorReason.MEMBER_NOT_FOUND, gapi_errors.ErrorReason.INVALID_MEMBER ], name=name, body=body) except (gapi_errors.GapiMemberNotFoundError, gapi_errors.GapiInvalidMemberError) as e: print( f' Group: {group}, {users_email[0]} Update to {role} Failed: {str(e)}' ) break print( f' Group: {group}, {users_email[0]} Updated to {role}' ) else: # clear roles = [] i = 5 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg.upper() in [ROLE_OWNER, ROLE_MANAGER, ROLE_MEMBER]: roles.append(myarg.upper()) i += 1 else: controlflow.invalid_argument_exit( sys.argv[i], 'gam update cigroup clear') if not roles: roles = [ROLE_MEMBER] group = gam.normalizeEmailAddressOrUID(group) member_type_message = f'{",".join(roles).lower()}s' sys.stderr.write( f'Getting {member_type_message} of {group} (may take some time for large groups)...\n' ) page_message = gapi.got_total_items_msg(f'{member_type_message}', '...') try: result = gapi.get_all_pages( ci.groups().memberships(), 'list', 'memberships', page_message=page_message, throw_reasons=gapi_errors.MEMBERS_THROW_REASONS, parent=parent, fields='nextPageToken,memberships(preferredMemberKey,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] sys.stderr.write( f'Group: {group}, Will remove {len(users_email)} {", ".join(roles).lower()}s.\n' ) for user_email in users_email: items.append([ 'gam', 'update', 'cigroup', group, 'remove', user_email ]) except (gapi_errors.GapiGroupNotFoundError, gapi_errors.GapiDomainNotFoundError, gapi_errors.GapiInvalidError, gapi_errors.GapiForbiddenError): gam.entityUnknownWarning('Group', group, 0, 0) if items: gam.run_batch(items) else: i = 4 body = {} sec_body = {} while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'name': body['displayName'] = sys.argv[i + 1] i += 2 elif myarg == 'description': body['description'] = sys.argv[i + 1] i += 2 elif myarg == 'security': body['labels'] = { 'cloudidentity.googleapis.com/groups.security': '', 'cloudidentity.googleapis.com/groups.discussion_forum': '' } i += 1 elif myarg in ['dynamic']: body['dynamicGroupMetadata'] = { 'queries': [{ 'query': sys.argv[i + 1], 'resourceType': 'USER' }] } i += 2 elif myarg in ['memberrestriction', 'memberrestrictions']: query = sys.argv[i + 1] member_types = { 'USER': '******', 'SERVICE_ACCOUNT': '2', 'GROUP': '3', } for key, val in member_types.items(): query = query.replace(key, val) sec_body['memberRestriction'] = {'query': query} i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam update cigroup') if body: updateMask = ','.join(body.keys()) name = group_email_to_id(ci, group) print(f'Updating group {group}') gapi.call(ci.groups(), 'patch', updateMask=updateMask, name=name, body=body) if sec_body: updateMask = 'member_restriction.query' # it seems like a bug that API requires /securitySettings # appended to name. We'll see if Google servers change this # at some point. name = f'{group_email_to_id(ci, group)}/securitySettings' print(f'Updating group {group} security settings') gapi.call(ci.groups(), 'updateSecuritySettings', name=name, updateMask=updateMask, body=sec_body)
def print_members(): ci = gapi_cloudidentity.build('cloudidentity_beta') todrive = False gapi_directory_customer.setTrueCustomerId() parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}' usemember = None roles = [] titles = ['group'] csvRows = [] groups_to_get = [] i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'todrive': todrive = True i += 1 elif myarg in ['role', 'roles']: for role in sys.argv[i + 1].lower().replace(',', ' ').split(): if role in GROUP_ROLES_MAP: roles.append(GROUP_ROLES_MAP[role]) else: controlflow.system_error_exit( 2, f'{role} is not a valid role for "gam print group-members {myarg}"' ) i += 2 elif myarg == 'enterprisemember': member = gam.convertUIDtoEmailAddress( sys.argv[i + 1], email_types=['user', 'group']) usemember = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels" i += 2 elif myarg in ['cigroup', 'cigroups']: group_email = gam.normalizeEmailAddressOrUID(sys.argv[i + 1]) groups_to_get = [group_email] i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam print cigroup-members') if not groups_to_get: gam.printGettingAllItems('Groups', usemember) page_message = gapi.got_total_items_first_last_msg('Groups') if usemember: try: groups_to_get = gapi.get_all_pages( ci.groups().memberships(), 'searchTransitiveGroups', 'memberships', throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O], message_attribute=['groupKey', 'id'], page_message=page_message, parent='groups/-', query=usemember, pageSize=1000, fields= 'nextPageToken,memberships(groupKey(id),relationType)') except googleapiclient.errors.HttpError: controlflow.system_error_exit( 2, f'enterprisemember requires Enterprise license') groups_to_get = [ group['groupKey']['id'] for group in groups_to_get if group['relationType'] == 'DIRECT' ] else: groups_to_get = gapi.get_all_pages( ci.groups(), 'list', 'groups', message_attribute=['groupKey', 'id'], page_message=page_message, parent=parent, view='BASIC', pageSize=1000, fields='nextPageToken,groups(groupKey(id))') groups_to_get = [ group['groupKey']['id'] for group in groups_to_get ] i = 0 count = len(groups_to_get) for group_email in groups_to_get: i += 1 sys.stderr.write( f'Getting members for {group_email}{gam.currentCountNL(i, count)}') group_id = group_email_to_id(ci, group_email) print(f'Getting members of cigroup {group_email}...') page_message = f' {gapi.got_total_items_first_last_msg("Members")}' group_members = gapi.get_all_pages( ci.groups().memberships(), 'list', 'memberships', soft_errors=True, parent=group_id, view='FULL', pageSize=500, page_message=page_message, message_attribute=['memberKey', 'id']) #fields='nextPageToken,memberships(memberKey,roles,createTime,updateTime)') if roles: group_members = filter_members_to_roles(group_members, roles) for member in group_members: # reduce role to a single value member['role'] = get_single_role(member.pop('roles')) member = utils.flatten_json(member) for title in member: if title not in titles: titles.append(title) member['group'] = group_email csvRows.append(member) display.write_csv_file(csvRows, titles, 'Group Members', todrive)
def print_(): cd = gapi_directory.build() roleId = None todrive = False kwargs = {} item_fields = [ 'roleAssignmentId', 'roleId', 'assignedTo', 'scopeType', 'orgUnitId' ] titles = [ 'roleAssignmentId', 'roleId', 'role', 'assignedTo', 'assignedToUser', 'scopeType', 'orgUnitId', 'orgUnit' ] csvRows = [] i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == 'user': kwargs['userKey'] = gam.normalizeEmailAddressOrUID(sys.argv[i + 1]) i += 2 elif myarg == 'role': roleId = gapi_directory_roles.getRoleId(sys.argv[i + 1]) i += 2 elif myarg == 'condition': cd = gapi_directory.build_beta() item_fields.append('condition') i += 1 elif myarg == 'todrive': todrive = True i += 1 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam print admins') fields = f'nextPageToken,items({",".join(item_fields)})' if roleId and not kwargs: kwargs['roleId'] = roleId roleId = None admins = gapi.get_all_pages(cd.roleAssignments(), 'list', 'items', customer=GC_Values[GC_CUSTOMER_ID], fields=fields, **kwargs) for admin in admins: if roleId and roleId != admin['roleId']: continue admin_attrib = {} for key, value in list(admin.items()): if key == 'assignedTo': admin_attrib['assignedToUser'] = gam.user_from_userid(value) elif key == 'roleId': admin_attrib['role'] = gapi_directory_roles.role_from_roleid( value) elif key == 'orgUnitId': value = f'id:{value}' admin_attrib[ 'orgUnit'] = gapi_directory_orgunits.orgunit_from_orgunitid( value, cd) elif key == 'condition': if value == SECURITY_GROUP_CONDITION: value = 'securitygroup' elif value == NONSECURITY_GROUP_CONDITION: value = 'nonsecuritygroup' if key not in titles: titles.append(key) admin_attrib[key] = value csvRows.append(admin_attrib) display.write_csv_file(csvRows, titles, 'Admins', todrive)
def showReport(): rep = buildGAPIObject() throw_reasons = [gapi.errors.ErrorReason.INVALID] report = sys.argv[2].lower() report = REPORT_CHOICE_MAP.get(report.replace('_', ''), report) if report == 'usage': showUsage() return if report == 'usageparameters': showUsageParameters() return valid_apps = gapi.get_enum_values_minus_unspecified( rep._rootDesc['resources']['activities']['methods']['list'] ['parameters']['applicationName']['enum']) + ['customer', 'user'] if report not in valid_apps: controlflow.expected_argument_exit('report', ', '.join(sorted(valid_apps)), report) customerId = GC_Values[GC_CUSTOMER_ID] if customerId == MY_CUSTOMER: customerId = None filters = parameters = actorIpAddress = startTime = endTime = eventName = orgUnitId = None tryDate = datetime.date.today().strftime(YYYYMMDD_FORMAT) to_drive = False userKey = 'all' fullDataRequired = None i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == 'date': tryDate = utils.get_yyyymmdd(sys.argv[i + 1]) i += 2 elif myarg in ['orgunit', 'org', 'ou']: _, orgUnitId = gam.getOrgUnitId(sys.argv[i + 1]) i += 2 elif myarg == 'fulldatarequired': fullDataRequired = [] fdr = sys.argv[i + 1].lower() if fdr and fdr == 'all': fullDataRequired = 'all' else: fullDataRequired = fdr.replace(',', ' ').split() i += 2 elif myarg == 'start': startTime = utils.get_time_or_delta_from_now(sys.argv[i + 1]) i += 2 elif myarg == 'end': endTime = utils.get_time_or_delta_from_now(sys.argv[i + 1]) i += 2 elif myarg == 'event': eventName = sys.argv[i + 1] i += 2 elif myarg == 'user': userKey = sys.argv[i + 1].lower() if userKey != 'all': userKey = gam.normalizeEmailAddressOrUID(sys.argv[i + 1]) i += 2 elif myarg in ['filter', 'filters']: filters = sys.argv[i + 1] i += 2 elif myarg in ['fields', 'parameters']: parameters = sys.argv[i + 1] i += 2 elif myarg == 'ip': actorIpAddress = sys.argv[i + 1] i += 2 elif myarg == 'todrive': to_drive = True i += 1 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam report') if report == 'user': while True: try: one_page = gapi.call(rep.userUsageReport(), 'get', throw_reasons=throw_reasons, date=tryDate, userKey=userKey, customerId=customerId, orgUnitID=orgUnitId, fields='warnings,usageReports', maxResults=1) warnings = one_page.get('warnings', []) has_reports = bool(one_page.get('usageReports')) fullData, tryDate = _check_full_data_available( warnings, tryDate, fullDataRequired, has_reports) if fullData < 0: print('No user report available.') sys.exit(1) if fullData == 0: continue page_message = gapi.got_total_items_msg('Users', '...\n') usage = gapi.get_all_pages(rep.userUsageReport(), 'get', 'usageReports', page_message=page_message, throw_reasons=throw_reasons, date=tryDate, userKey=userKey, customerId=customerId, orgUnitID=orgUnitId, filters=filters, parameters=parameters) break except gapi.errors.GapiInvalidError as e: tryDate = _adjust_date(str(e)) if not usage: print('No user report available.') sys.exit(1) titles = ['email', 'date'] csvRows = [] for user_report in usage: if 'entity' not in user_report: continue row = {'email': user_report['entity']['userEmail'], 'date': tryDate} for item in user_report.get('parameters', []): if 'name' not in item: continue name = item['name'] if not name in titles: titles.append(name) for ptype in REPORTS_PARAMETERS_SIMPLE_TYPES: if ptype in item: row[name] = item[ptype] break else: row[name] = '' csvRows.append(row) display.write_csv_file(csvRows, titles, f'User Reports - {tryDate}', to_drive) elif report == 'customer': while True: try: first_page = gapi.call(rep.customerUsageReports(), 'get', throw_reasons=throw_reasons, customerId=customerId, date=tryDate, fields='warnings,usageReports') warnings = first_page.get('warnings', []) has_reports = bool(first_page.get('usageReports')) fullData, tryDate = _check_full_data_available( warnings, tryDate, fullDataRequired, has_reports) if fullData < 0: print('No customer report available.') sys.exit(1) if fullData == 0: continue usage = gapi.get_all_pages(rep.customerUsageReports(), 'get', 'usageReports', throw_reasons=throw_reasons, customerId=customerId, date=tryDate, parameters=parameters) break except gapi.errors.GapiInvalidError as e: tryDate = _adjust_date(str(e)) if not usage: print('No customer report available.') sys.exit(1) titles = ['name', 'value', 'client_id'] csvRows = [] auth_apps = list() for item in usage[0]['parameters']: if 'name' not in item: continue name = item['name'] if 'intValue' in item: value = item['intValue'] elif 'msgValue' in item: if name == 'accounts:authorized_apps': for subitem in item['msgValue']: app = {} for an_item in subitem: if an_item == 'client_name': app['name'] = 'App: ' + \ subitem[an_item].replace('\n', '\\n') elif an_item == 'num_users': app['value'] = f'{subitem[an_item]} users' elif an_item == 'client_id': app['client_id'] = subitem[an_item] auth_apps.append(app) continue values = [] for subitem in item['msgValue']: if 'count' in subitem: mycount = myvalue = None for key, value in list(subitem.items()): if key == 'count': mycount = value else: myvalue = value if mycount and myvalue: values.append(f'{myvalue}:{mycount}') value = ' '.join(values) elif 'version_number' in subitem \ and 'num_devices' in subitem: values.append(f'{subitem["version_number"]}:' f'{subitem["num_devices"]}') else: continue value = ' '.join(sorted(values, reverse=True)) csvRows.append({'name': name, 'value': value}) for app in auth_apps: # put apps at bottom csvRows.append(app) display.write_csv_file(csvRows, titles, f'Customer Report - {tryDate}', todrive=to_drive) else: page_message = gapi.got_total_items_msg('Activities', '...\n') activities = gapi.get_all_pages(rep.activities(), 'list', 'items', page_message=page_message, applicationName=report, userKey=userKey, customerId=customerId, actorIpAddress=actorIpAddress, startTime=startTime, endTime=endTime, eventName=eventName, filters=filters, orgUnitID=orgUnitId) if activities: titles = ['name'] csvRows = [] for activity in activities: events = activity['events'] del activity['events'] activity_row = utils.flatten_json(activity) purge_parameters = True for event in events: for item in event.get('parameters', []): if set(item) == set(['value', 'name']): event[item['name']] = item['value'] elif set(item) == set(['intValue', 'name']): if item['name'] in ['start_time', 'end_time']: val = item.get('intValue') if val is not None: val = int(val) if val >= 62135683200: event[item['name']] = \ datetime.datetime.fromtimestamp( val-62135683200).isoformat() else: event[item['name']] = item['intValue'] elif set(item) == set(['boolValue', 'name']): event[item['name']] = item['boolValue'] elif set(item) == set(['multiValue', 'name']): event[item['name']] = ' '.join(item['multiValue']) elif item['name'] == 'scope_data': parts = {} for message in item['multiMessageValue']: for mess in message['parameter']: value = mess.get( 'value', ' '.join(mess.get('multiValue', []))) parts[mess['name']] = parts.get( mess['name'], []) + [value] for part, v in parts.items(): if part == 'scope_name': part = 'scope' event[part] = ' '.join(v) else: purge_parameters = False if purge_parameters: event.pop('parameters', None) row = utils.flatten_json(event) row.update(activity_row) for item in row: if item not in titles: titles.append(item) csvRows.append(row) display.sort_csv_titles([ 'name', ], titles) display.write_csv_file(csvRows, titles, f'{report.capitalize()} Activity Report', to_drive)