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 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)