Files
GoogleDriveManagement/src/gam/gapi/directory/groups.py
Ross Scroggs cc60095344 Map / to %2F in group email address for Group Settings API (#1288)
* Map / to %2F in group email address for Group Settings API

* Make update deviceuserstate consistent with other deviceuser commands

gam update deviceuserstate [id] <DeviceUserID> ...
2020-12-06 20:56:49 -05:00

1249 lines
50 KiB
Python

import sys
import gam
from gam.var import *
from gam import controlflow
from gam import display
from gam import gapi
from gam.gapi import directory as gapi_directory
from gam.gapi import errors as gapi_errors
from gam.gapi.directory import customer as gapi_directory_customer
from gam import utils
def GroupIsAbuseOrPostmaster(emailAddr):
return emailAddr.startswith('abuse@') or emailAddr.startswith('postmaster@')
def mapGroupEmailForSettings(emailAddr):
return emailAddr.replace('/', '%2F')
def create():
cd = gapi_directory.build()
body = {'email': gam.normalizeEmailAddressOrUID(sys.argv[3], noUid=True)}
gs_get_before_update = got_name = False
i = 4
gs_body = {}
gs = None
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'name':
body['name'] = sys.argv[i + 1]
got_name = True
i += 2
elif myarg == 'description':
description = sys.argv[i + 1].replace('\\n', '\n')
# The Directory API Groups insert method can not handle any of
# these characters ('\n<>=') in the description field. If any of
# these characters are present, use the Group Settings API to set
# the description.
for c in '\n<>=':
if description.find(c) != -1:
gs_body['description'] = description
if not gs:
gs = gam.buildGAPIObject('groupssettings')
gs_object = gs._rootDesc
break
else:
body['description'] = description
i += 2
elif myarg == 'getbeforeupdate':
gs_get_before_update = True
i += 1
else:
if not gs:
gs = gam.buildGAPIObject('groupssettings')
gs_object = gs._rootDesc
getGroupAttrValue(myarg, sys.argv[i + 1], gs_object, gs_body,
'create')
i += 2
if not got_name:
body['name'] = body['email']
print(f'Creating group {body["email"]}')
gapi.call(cd.groups(), 'insert', body=body, fields='email')
if gs and not GroupIsAbuseOrPostmaster(body['email']):
if gs_get_before_update:
current_settings = gapi.call(
gs.groups(),
'get',
retry_reasons=[
gapi_errors.ErrorReason.SERVICE_LIMIT,
gapi_errors.ErrorReason.NOT_FOUND
],
groupUniqueId=mapGroupEmailForSettings(body['email']),
fields='*')
if current_settings is not None:
gs_body = dict(
list(current_settings.items()) + list(gs_body.items()))
if gs_body:
gapi.call(gs.groups(),
'update',
groupUniqueId=mapGroupEmailForSettings(body['email']),
retry_reasons=[
gapi_errors.ErrorReason.SERVICE_LIMIT,
gapi_errors.ErrorReason.NOT_FOUND
],
body=gs_body)
def delete():
cd = gapi_directory.build()
group = gam.normalizeEmailAddressOrUID(sys.argv[3])
print(f'Deleting group {group}')
gapi.call(cd.groups(), 'delete', groupKey=group)
def deleteUserFromGroups(users):
cd = gapi_directory.build()
for user in users:
user_groups = gapi.get_all_pages(cd.groups(),
'list',
'groups',
userKey=user,
fields='groups(id,email)')
jcount = len(user_groups)
print(f'{user} is in {jcount} groups')
j = 0
for user_group in user_groups:
j += 1
group_email = user_group['email']
current_count = gam.currentCount(j, jcount)
print(f' removing {user} from {group_email} {current_count}')
gapi.call(cd.members(),
'delete',
soft_errors=True,
groupKey=user_group['id'],
memberKey=user)
print('')
def exists(cd, group, i=0, count=0):
group = gam.normalizeEmailAddressOrUID(group)
try:
return gapi.call(cd.groups(),
'get',
throw_reasons=gapi_errors.GROUP_GET_THROW_REASONS,
retry_reasons=gapi_errors.GROUP_GET_RETRY_REASONS,
groupKey=group,
fields='email')['email']
except (gapi_errors.GapiGroupNotFoundError,
gapi_errors.GapiDomainNotFoundError,
gapi_errors.GapiDomainCannotUseApisError,
gapi_errors.GapiForbiddenError, gapi_errors.GapiBadRequestError):
gam.entityUnknownWarning('Group', group, i, count)
return None
def info(group_name=None):
cd = gapi_directory.build()
gs = gam.buildGAPIObject('groupssettings')
getAliases = getUsers = True
getGroups = False
if group_name is None:
group_name = gam.normalizeEmailAddressOrUID(sys.argv[3])
i = 4
else:
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'nousers':
getUsers = False
i += 1
elif myarg == 'noaliases':
getAliases = False
i += 1
elif myarg == 'groups':
getGroups = True
i += 1
elif myarg in [
'nogroups', 'nolicenses', 'nolicences', 'noschemas', 'schemas',
'userview'
]:
i += 1
if myarg == 'schemas':
i += 1
else:
controlflow.invalid_argument_exit(myarg, 'gam info group')
basic_info = gapi.call(cd.groups(), 'get', groupKey=group_name)
settings = {}
if not GroupIsAbuseOrPostmaster(basic_info['email']):
try:
settings = gapi.call(
gs.groups(),
'get',
throw_reasons=[gapi_errors.ErrorReason.AUTH_ERROR],
retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
groupUniqueId=mapGroupEmailForSettings(basic_info['email'])
) # Use email address retrieved from cd since GS API doesn't support uid
if settings is None:
settings = {}
except gapi_errors.GapiAuthErrorError:
pass
print('')
print('Group Settings:')
for key, value in list(basic_info.items()):
if (key in ['kind', 'etag']) or ((key == 'aliases') and
(not getAliases)):
continue
if isinstance(value, list):
print(f' {key}:')
for val in value:
print(f' {val}')
else:
print(f' {key}: {value}')
for key, value in list(settings.items()):
if key in ['kind', 'etag', 'description', 'email', 'name']:
continue
print(f' {key}: {value}')
if getGroups:
groups = gapi.get_all_pages(cd.groups(),
'list',
'groups',
userKey=basic_info['email'],
fields='nextPageToken,groups(name,email)')
if groups:
print(f'Groups: ({len(groups)})')
for groupm in groups:
print(f' {groupm["name"]}: {groupm["email"]}')
if getUsers:
members = gapi.get_all_pages(
cd.members(),
'list',
'members',
groupKey=group_name,
fields='nextPageToken,members(email,id,role,type)')
print('Members:')
for member in members:
print(
f' {member.get("role", ROLE_MEMBER).lower()}: {member.get("email", member["id"])} ({member["type"].lower()})'
)
print(f'Total {len(members)} users in group')
def info_member():
cd = gapi_directory.build()
memberKey = gam.normalizeEmailAddressOrUID(sys.argv[3])
groupKey = gam.normalizeEmailAddressOrUID(sys.argv[4])
member_info = gapi.call(cd.members(),
'get',
memberKey=memberKey,
groupKey=groupKey)
display.print_json(member_info)
GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP = {
'admincreated': ['adminCreated', 'Admin_Created'],
'aliases': [
'aliases', 'Aliases', 'nonEditableAliases', 'NonEditableAliases'
],
'description': ['description', 'Description'],
'directmemberscount': ['directMembersCount', 'DirectMembersCount'],
'email': ['email', 'Email'],
'id': ['id', 'ID'],
'name': ['name', 'Name'],
}
GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP = {
'allowexternalmembers':
'allowExternalMembers',
'allowgooglecommunication':
'allowGoogleCommunication',
'allowwebposting':
'allowWebPosting',
'archiveonly':
'archiveOnly',
'customfootertext':
'customFooterText',
'customreplyto':
'customReplyTo',
'defaultmessagedenynotificationtext':
'defaultMessageDenyNotificationText',
'enablecollaborativeinbox':
'enableCollaborativeInbox',
'favoriterepliesontop':
'favoriteRepliesOnTop',
'gal':
'includeInGlobalAddressList',
'includecustomfooter':
'includeCustomFooter',
'includeinglobaladdresslist':
'includeInGlobalAddressList',
'isarchived':
'isArchived',
'memberscanpostasthegroup':
'membersCanPostAsTheGroup',
'messagemoderationlevel':
'messageModerationLevel',
'primarylanguage':
'primaryLanguage',
'replyto':
'replyTo',
'sendmessagedenynotification':
'sendMessageDenyNotification',
'showingroupdirectory':
'showInGroupDirectory',
'spammoderationlevel':
'spamModerationLevel',
'whocanadd':
'whoCanAdd',
'whocanapprovemembers':
'whoCanApproveMembers',
'whocanapprovemessages':
'whoCanApproveMessages',
'whocanassigntopics':
'whoCanAssignTopics',
'whocanassistcontent':
'whoCanAssistContent',
'whocanbanusers':
'whoCanBanUsers',
'whocancontactowner':
'whoCanContactOwner',
'whocandeleteanypost':
'whoCanDeleteAnyPost',
'whocandeletetopics':
'whoCanDeleteTopics',
'whocandiscovergroup':
'whoCanDiscoverGroup',
'whocanenterfreeformtags':
'whoCanEnterFreeFormTags',
'whocanhideabuse':
'whoCanHideAbuse',
'whocaninvite':
'whoCanInvite',
'whocanjoin':
'whoCanJoin',
'whocanleavegroup':
'whoCanLeaveGroup',
'whocanlocktopics':
'whoCanLockTopics',
'whocanmaketopicssticky':
'whoCanMakeTopicsSticky',
'whocanmarkduplicate':
'whoCanMarkDuplicate',
'whocanmarkfavoritereplyonanytopic':
'whoCanMarkFavoriteReplyOnAnyTopic',
'whocanmarkfavoritereplyonowntopic':
'whoCanMarkFavoriteReplyOnOwnTopic',
'whocanmarknoresponseneeded':
'whoCanMarkNoResponseNeeded',
'whocanmoderatecontent':
'whoCanModerateContent',
'whocanmoderatemembers':
'whoCanModerateMembers',
'whocanmodifymembers':
'whoCanModifyMembers',
'whocanmodifytagsandcategories':
'whoCanModifyTagsAndCategories',
'whocanmovetopicsin':
'whoCanMoveTopicsIn',
'whocanmovetopicsout':
'whoCanMoveTopicsOut',
'whocanpostannouncements':
'whoCanPostAnnouncements',
'whocanpostmessage':
'whoCanPostMessage',
'whocantaketopics':
'whoCanTakeTopics',
'whocanunassigntopic':
'whoCanUnassignTopic',
'whocanunmarkfavoritereplyonanytopic':
'whoCanUnmarkFavoriteReplyOnAnyTopic',
'whocanviewgroup':
'whoCanViewGroup',
'whocanviewmembership':
'whoCanViewMembership',
}
def print_():
cd = gapi_directory.build()
i = 3
members = membersCountOnly = managers = managersCountOnly = owners = ownersCountOnly = False
customer = GC_Values[GC_CUSTOMER_ID]
usedomain = usemember = usequery = None
aliasDelimiter = ' '
memberDelimiter = '\n'
todrive = False
cdfieldsList = []
gsfieldsList = []
fieldsTitles = {}
titles = []
csvRows = []
display.add_field_title_to_csv_file('email',
GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP,
cdfieldsList, fieldsTitles, titles)
roles = []
getSettings = sortHeaders = False
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'todrive':
todrive = True
i += 1
elif myarg == 'domain':
usedomain = sys.argv[i + 1].lower()
customer = None
i += 2
elif myarg == 'member':
usemember = gam.normalizeEmailAddressOrUID(sys.argv[i + 1])
customer = usequery = None
i += 2
elif myarg == 'query':
usequery = sys.argv[i + 1]
usemember = None
i += 2
elif myarg == 'maxresults':
# deprecated argument
i += 2
elif myarg == 'delimiter':
aliasDelimiter = memberDelimiter = sys.argv[i + 1]
i += 2
elif myarg in GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP:
display.add_field_title_to_csv_file(
myarg, GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP, cdfieldsList,
fieldsTitles, titles)
i += 1
elif myarg == 'settings':
getSettings = True
i += 1
elif myarg == 'allfields':
getSettings = sortHeaders = True
cdfieldsList = []
gsfieldsList = []
fieldsTitles = {}
for field in GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP:
display.add_field_title_to_csv_file(
field, GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP, cdfieldsList,
fieldsTitles, titles)
i += 1
elif myarg == 'sortheaders':
sortHeaders = True
i += 1
elif myarg == 'fields':
fieldNameList = sys.argv[i + 1]
for field in fieldNameList.lower().replace(',', ' ').split():
if field in GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP:
display.add_field_title_to_csv_file(
field, GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP,
cdfieldsList, fieldsTitles, titles)
elif field in GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP:
display.add_field_to_csv_file(field, {
field:
[GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP[field]]
}, gsfieldsList, fieldsTitles, titles)
elif field == 'collaborative':
for attrName in COLLABORATIVE_INBOX_ATTRIBUTES:
display.add_field_to_csv_file(attrName,
{attrName: [attrName]},
gsfieldsList,
fieldsTitles, titles)
else:
controlflow.invalid_argument_exit(
field, 'gam print groups fields')
i += 2
elif myarg in ['members', 'memberscount']:
roles.append(ROLE_MEMBER)
members = True
if myarg == 'memberscount':
membersCountOnly = True
i += 1
elif myarg in ['owners', 'ownerscount']:
roles.append(ROLE_OWNER)
owners = True
if myarg == 'ownerscount':
ownersCountOnly = True
i += 1
elif myarg in ['managers', 'managerscount']:
roles.append(ROLE_MANAGER)
managers = True
if myarg == 'managerscount':
managersCountOnly = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam print groups')
cdfields = ','.join(set(cdfieldsList))
if gsfieldsList:
getSettings = True
gsfields = ','.join(set(gsfieldsList))
elif getSettings:
gsfields = None
if getSettings:
gs = gam.buildGAPIObject('groupssettings')
roles = ','.join(sorted(set(roles)))
if roles:
if members:
display.add_titles_to_csv_file([
'MembersCount',
], titles)
if not membersCountOnly:
display.add_titles_to_csv_file([
'Members',
], titles)
if managers:
display.add_titles_to_csv_file([
'ManagersCount',
], titles)
if not managersCountOnly:
display.add_titles_to_csv_file([
'Managers',
], titles)
if owners:
display.add_titles_to_csv_file([
'OwnersCount',
], titles)
if not ownersCountOnly:
display.add_titles_to_csv_file([
'Owners',
], titles)
gam.printGettingAllItems('Groups', None)
page_message = gapi.got_total_items_first_last_msg('Groups')
entityList = gapi.get_all_pages(cd.groups(),
'list',
'groups',
page_message=page_message,
message_attribute='email',
customer=customer,
domain=usedomain,
userKey=usemember,
query=usequery,
fields=f'nextPageToken,groups({cdfields})')
i = 0
count = len(entityList)
for groupEntity in entityList:
i += 1
groupEmail = groupEntity['email']
group = {}
for field in cdfieldsList:
if field in groupEntity:
if isinstance(groupEntity[field], list):
group[fieldsTitles[field]] = aliasDelimiter.join(
groupEntity[field])
else:
group[fieldsTitles[field]] = groupEntity[field]
if roles:
sys.stderr.write(
f' Getting {roles} for {groupEmail}{gam.currentCountNL(i, count)}')
page_message = gapi.got_total_items_first_last_msg('Members')
validRoles, listRoles, listFields = gam._getRoleVerification(
roles, 'nextPageToken,members(email,id,role)')
groupMembers = gapi.get_all_pages(cd.members(),
'list',
'members',
page_message=page_message,
message_attribute='email',
soft_errors=True,
groupKey=groupEmail,
roles=listRoles,
fields=listFields)
if members:
membersList = []
membersCount = 0
if managers:
managersList = []
managersCount = 0
if owners:
ownersList = []
ownersCount = 0
for member in groupMembers:
member_email = member.get('email', member.get('id', None))
if not member_email:
sys.stderr.write(f' Not sure what to do with: {member}')
continue
role = member.get('role', ROLE_MEMBER)
if not validRoles or role in validRoles:
if role == ROLE_MEMBER:
if members:
membersCount += 1
if not membersCountOnly:
membersList.append(member_email)
elif role == ROLE_MANAGER:
if managers:
managersCount += 1
if not managersCountOnly:
managersList.append(member_email)
elif role == ROLE_OWNER:
if owners:
ownersCount += 1
if not ownersCountOnly:
ownersList.append(member_email)
elif members:
membersCount += 1
if not membersCountOnly:
membersList.append(member_email)
if members:
group['MembersCount'] = membersCount
if not membersCountOnly:
group['Members'] = memberDelimiter.join(membersList)
if managers:
group['ManagersCount'] = managersCount
if not managersCountOnly:
group['Managers'] = memberDelimiter.join(managersList)
if owners:
group['OwnersCount'] = ownersCount
if not ownersCountOnly:
group['Owners'] = memberDelimiter.join(ownersList)
if getSettings and not GroupIsAbuseOrPostmaster(groupEmail):
sys.stderr.write(
f' Retrieving Settings for group {groupEmail}{gam.currentCountNL(i, count)}'
)
settings = gapi.call(gs.groups(),
'get',
soft_errors=True,
retry_reasons=[
gapi_errors.ErrorReason.SERVICE_LIMIT,
gapi_errors.ErrorReason.INVALID
],
groupUniqueId=mapGroupEmailForSettings(groupEmail),
fields=gsfields)
if settings:
for key in settings:
if key in ['email', 'name', 'description', 'kind', 'etag']:
continue
setting_value = settings[key]
if setting_value is None:
setting_value = ''
if key not in titles:
titles.append(key)
group[key] = setting_value
else:
sys.stderr.write(
f' Settings unavailable for group {groupEmail}{gam.currentCountNL(i, count)}'
)
csvRows.append(group)
if sortHeaders:
display.sort_csv_titles([
'Email',
], titles)
display.write_csv_file(csvRows, titles, 'Groups', todrive)
def print_members():
cd = gapi_directory.build()
todrive = False
membernames = False
includeDerivedMembership = False
customer = GC_Values[GC_CUSTOMER_ID]
checkSuspended = usedomain = usemember = usequery = None
roles = []
fields = 'nextPageToken,members(email,id,role,status,type)'
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 == 'domain':
usedomain = sys.argv[i + 1].lower()
customer = None
i += 2
elif myarg == 'member':
usemember = gam.normalizeEmailAddressOrUID(sys.argv[i + 1])
customer = usequery = None
i += 2
elif myarg == 'query':
usequery = sys.argv[i + 1]
usemember = None
i += 2
elif myarg == 'fields':
memberFieldsList = sys.argv[i + 1].replace(',', ' ').lower().split()
fields = f'nextPageToken,members({",".join(memberFieldsList)})'
i += 2
elif myarg == 'membernames':
membernames = True
titles.append('name')
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 ['group', 'groupns', 'groupsusp']:
group_email = gam.normalizeEmailAddressOrUID(sys.argv[i + 1])
groups_to_get = [{'email': group_email}]
if myarg == 'groupns':
checkSuspended = False
elif myarg == 'groupsusp':
checkSuspended = True
i += 2
elif myarg in ['suspended', 'notsuspended']:
checkSuspended = myarg == 'suspended'
i += 1
elif myarg == 'includederivedmembership':
includeDerivedMembership = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam print group-members')
if not groups_to_get:
groups_to_get = gapi.get_all_pages(cd.groups(),
'list',
'groups',
message_attribute='email',
customer=customer,
domain=usedomain,
userKey=usemember,
query=usequery,
fields='nextPageToken,groups(email)')
i = 0
count = len(groups_to_get)
for group in groups_to_get:
i += 1
group_email = group['email']
sys.stderr.write(
f'Getting members for {group_email}{gam.currentCountNL(i, count)}')
validRoles, listRoles, listFields = gam._getRoleVerification(
','.join(roles), fields)
group_members = gapi.get_all_pages(
cd.members(),
'list',
'members',
soft_errors=True,
includeDerivedMembership=includeDerivedMembership,
groupKey=group_email,
roles=listRoles,
fields=listFields)
for member in group_members:
if not _checkMemberRoleIsSuspended(member, validRoles,
checkSuspended):
continue
for title in member:
if title not in titles:
titles.append(title)
member['group'] = group_email
if membernames and 'type' in member and 'id' in member:
if member['type'] == 'USER':
try:
mbinfo = gapi.call(
cd.users(),
'get',
throw_reasons=[
gapi_errors.ErrorReason.USER_NOT_FOUND,
gapi_errors.ErrorReason.NOT_FOUND,
gapi_errors.ErrorReason.FORBIDDEN
],
userKey=member['id'],
fields='name')
memberName = mbinfo['name']['fullName']
except (gapi_errors.GapiUserNotFoundError,
gapi_errors.GapiNotFoundError,
gapi_errors.GapiForbiddenError):
memberName = 'Unknown'
elif member['type'] == 'GROUP':
try:
mbinfo = gapi.call(
cd.groups(),
'get',
throw_reasons=[
gapi_errors.ErrorReason.NOT_FOUND,
gapi_errors.ErrorReason.FORBIDDEN
],
groupKey=member['id'],
fields='name')
memberName = mbinfo['name']
except (gapi_errors.GapiNotFoundError,
gapi_errors.GapiForbiddenError):
memberName = 'Unknown'
elif member['type'] == 'CUSTOMER':
try:
mbinfo = gapi.call(
cd.customers(),
'get',
throw_reasons=[
gapi_errors.ErrorReason.BAD_REQUEST,
gapi_errors.ErrorReason.RESOURCE_NOT_FOUND,
gapi_errors.ErrorReason.FORBIDDEN
],
customerKey=member['id'],
fields='customerDomain')
memberName = mbinfo['customerDomain']
except (gapi_errors.GapiBadRequestError,
gapi_errors.GapiResourceNotFoundError,
gapi_errors.GapiForbiddenError):
memberName = 'Unknown'
else:
memberName = 'Unknown'
member['name'] = memberName
csvRows.append(member)
display.write_csv_file(csvRows, titles, 'Group Members', todrive)
def _checkMemberRoleIsSuspended(member, validRoles, isSuspended):
if validRoles and member.get('role', ROLE_MEMBER) not in validRoles:
return False
if isSuspended is None:
return True
memberStatus = member.get('status', 'UNKNOWN')
if not isSuspended:
return memberStatus != 'SUSPENDED'
return memberStatus == 'SUSPENDED'
UPDATE_GROUP_SUBCMDS = ['add', 'clear', 'delete', 'remove', 'sync', 'update']
GROUP_ROLES_MAP = {
'owner': ROLE_OWNER,
'owners': ROLE_OWNER,
'manager': ROLE_MANAGER,
'managers': ROLE_MANAGER,
'member': ROLE_MEMBER,
'members': ROLE_MEMBER,
}
MEMBER_DELIVERY_MAP = {
'allmail': 'ALL_MAIL',
'digest': 'DIGEST',
'daily': 'DAILY',
'abridged': 'DAILY',
'nomail': 'NONE',
'none': 'NONE'
}
def update():
# Convert foo@googlemail.com to foo@gmail.com; eliminate periods in name for foo.bar@gmail.com
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
delivery = 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().replace('_', '') in MEMBER_DELIVERY_MAP:
delivery = MEMBER_DELIVERY_MAP[sys.argv[i].lower().replace('_', '')]
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, delivery)
gs_get_before_update = False
cd = gapi_directory.build()
group = sys.argv[3]
myarg = sys.argv[4].lower()
items = []
if myarg in UPDATE_GROUP_SUBCMDS:
group = gam.normalizeEmailAddressOrUID(group)
if myarg == 'add':
role, users_email, delivery = _getRoleAndUsers()
if not role:
role = ROLE_MEMBER
if not exists(cd, group):
return
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', 'group', group, 'add', role]
if delivery:
item.append(delivery)
item.append(user_email)
items.append(item)
elif len(users_email) > 0:
body = {
'role':
role,
'email' if users_email[0].find('@') != -1 else 'id':
users_email[0]
}
add_text = [f'as {role}']
if delivery:
body['delivery_settings'] = delivery
add_text.append(f'delivery {delivery}')
for i in range(2):
try:
gapi.call(
cd.members(),
'insert',
throw_reasons=[
gapi_errors.ErrorReason.DUPLICATE,
gapi_errors.ErrorReason.MEMBER_NOT_FOUND,
gapi_errors.ErrorReason.RESOURCE_NOT_FOUND,
gapi_errors.ErrorReason.INVALID_MEMBER,
gapi_errors.ErrorReason.
CYCLIC_MEMBERSHIPS_NOT_ALLOWED
],
groupKey=group,
body=body)
print(
f' Group: {group}, {users_email[0]} Added {" ".join(add_text)}'
)
break
except gapi_errors.GapiDuplicateError as e:
# check if user is a full member, not pending
try:
result = gapi.call(
cd.members(),
'get',
throw_reasons=[
gapi_errors.ErrorReason.MEMBER_NOT_FOUND
],
memberKey=users_email[0],
groupKey=group,
fields='role')
print(
f' Group: {group}, {users_email[0]} Add {" ".join(add_text)} Failed: Duplicate, already a {result["role"]}'
)
break # if get succeeds, user is a full member and we throw duplicate error
except gapi_errors.GapiMemberNotFoundError:
# insert fails on duplicate and get fails on not found, user is pending
print(
f' Group: {group}, {users_email[0]} member is pending, deleting and re-adding to solve...'
)
gapi.call(cd.members(),
'delete',
memberKey=users_email[0],
groupKey=group)
continue # 2nd insert should succeed now that pending is clear
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, delivery = _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))
group = exists(cd, group)
if group:
currentMembersSet = set()
currentMembersMap = {}
for current_email in gam.getUsersToModify(
entity_type='group',
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))
# Compare incoming members and current members using the cleaned addresses; we actually add/remove with the original addresses
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', 'group', group, 'add']
if role:
item.append(role)
if delivery:
item.append(delivery)
item.append(user)
items.append(item)
for user in to_remove:
items.append(
['gam', 'update', 'group', group, 'remove', user])
elif myarg in ['delete', 'remove']:
_, users_email, _ = _getRoleAndUsers()
if not exists(cd, group):
return
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', 'group', group, 'remove', user_email])
elif len(users_email) > 0:
try:
gapi.call(cd.members(),
'delete',
throw_reasons=[
gapi_errors.ErrorReason.MEMBER_NOT_FOUND,
gapi_errors.ErrorReason.INVALID_MEMBER
],
groupKey=group,
memberKey=users_email[0])
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, delivery = _getRoleAndUsers()
group = exists(cd, group)
if group:
if not role and not delivery:
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', 'group', group, 'update']
if role:
item.append(role)
if delivery:
item.append(delivery)
item.append(user_email)
items.append(item)
elif len(users_email) > 0:
body = {}
update_text = []
if role:
body['role'] = role
update_text.append(f'to {role}')
if delivery:
body['delivery_settings'] = delivery
update_text.append(f'delivery {delivery}')
try:
gapi.call(cd.members(),
'update',
throw_reasons=[
gapi_errors.ErrorReason.MEMBER_NOT_FOUND,
gapi_errors.ErrorReason.INVALID_MEMBER
],
groupKey=group,
memberKey=users_email[0],
body=body)
print(
f' Group: {group}, {users_email[0]} Updated {" ".join(update_text)}'
)
except (gapi_errors.GapiMemberNotFoundError,
gapi_errors.GapiInvalidMemberError) as e:
print(
f' Group: {group}, {users_email[0]} Update to {role} Failed: {str(e)}'
)
else: # clear
checkSuspended = None
fields = ['email', 'id']
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
elif myarg in ['suspended', 'notsuspended']:
checkSuspended = myarg == 'suspended'
fields.append('status')
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam update group clear')
if roles:
roles = ','.join(sorted(set(roles)))
else:
roles = ROLE_MEMBER
group = gam.normalizeEmailAddressOrUID(group)
member_type_message = f'{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}',
'...')
validRoles, listRoles, listFields = gam._getRoleVerification(
roles, f'nextPageToken,members({",".join(fields)})')
try:
result = gapi.get_all_pages(
cd.members(),
'list',
'members',
page_message=page_message,
throw_reasons=gapi_errors.MEMBERS_THROW_REASONS,
groupKey=group,
roles=listRoles,
fields=listFields)
if not result:
print('Group already has 0 members')
return
users_email = [
member.get('email', member['id'])
for member in result
if _checkMemberRoleIsSuspended(member, validRoles,
checkSuspended)
]
if len(users_email) > 1:
sys.stderr.write(
f'Group: {group}, Will remove {len(users_email)} {"" if checkSuspended is None else ["Non-suspended ", "Suspended "][checkSuspended]}{roles}s.\n'
)
for user_email in users_email:
items.append([
'gam', 'update', 'group', group, 'remove',
user_email
])
elif len(users_email) > 0:
try:
gapi.call(cd.members(),
'delete',
throw_reasons=[
gapi_errors.ErrorReason.MEMBER_NOT_FOUND,
gapi_errors.ErrorReason.INVALID_MEMBER
],
groupKey=group,
memberKey=users_email[0])
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)}'
)
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
use_cd_api = False
gs = None
gs_body = {}
cd_body = {}
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'email':
use_cd_api = True
cd_body['email'] = gam.normalizeEmailAddressOrUID(sys.argv[i +
1])
i += 2
elif myarg == 'admincreated':
use_cd_api = True
cd_body['adminCreated'] = gam.getBoolean(sys.argv[i + 1], myarg)
i += 2
elif myarg == 'getbeforeupdate':
gs_get_before_update = True
i += 1
else:
if not gs:
gs = gam.buildGAPIObject('groupssettings')
gs_object = gs._rootDesc
getGroupAttrValue(myarg, sys.argv[i + 1], gs_object, gs_body,
'update')
i += 2
group = gam.normalizeEmailAddressOrUID(group)
if use_cd_api or (
group.find('@') == -1
): # group settings API won't take uid so we make sure cd API is used so that we can grab real email.
group = gapi.call(cd.groups(),
'update',
groupKey=group,
body=cd_body,
fields='email')['email']
if gs:
if not GroupIsAbuseOrPostmaster(group):
if gs_get_before_update:
current_settings = gapi.call(
gs.groups(),
'get',
retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
groupUniqueId=mapGroupEmailForSettings(group),
fields='*')
if current_settings is not None:
gs_body = dict(
list(current_settings.items()) +
list(gs_body.items()))
if gs_body:
gapi.call(
gs.groups(),
'update',
retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
groupUniqueId=mapGroupEmailForSettings(group),
body=gs_body)
print(f'updated group {group}')
GROUP_SETTINGS_LIST_PATTERN = re.compile(r'([A-Z][A-Z_]+[A-Z]?)')
def getGroupAttrValue(myarg, value, gs_object, gs_body, function):
if myarg == 'collaborative':
myarg = 'enablecollaborativeinbox'
for (attrib,
params) in list(gs_object['schemas']['Groups']['properties'].items()):
if attrib in ['kind', 'etag', 'email']:
continue
if myarg == attrib.lower():
if params['type'] == 'integer':
try:
if value[-1:].upper() == 'M':
value = int(value[:-1]) * 1024 * 1024
elif value[-1:].upper() == 'K':
value = int(value[:-1]) * 1024
elif value[-1].upper() == 'B':
value = int(value[:-1])
else:
value = int(value)
except ValueError:
controlflow.system_error_exit(
2,
f'{myarg} must be a number ending with M (megabytes), K (kilobytes) or nothing (bytes); got {value}'
)
elif params['type'] == 'string':
if attrib == 'description':
value = value.replace('\\n', '\n')
elif attrib == 'primaryLanguage':
value = LANGUAGE_CODES_MAP.get(value.lower(), value)
elif attrib in GROUP_SETTINGS_LIST_ATTRIBUTES:
value = value.upper()
possible_values = GROUP_SETTINGS_LIST_PATTERN.findall(
params['description'])
if value not in possible_values:
controlflow.expected_argument_exit(
f'value for {attrib}', ', '.join(possible_values),
value)
elif attrib in GROUP_SETTINGS_BOOLEAN_ATTRIBUTES:
value = value.lower()
if value in true_values:
value = 'true'
elif value in false_values:
value = 'false'
else:
controlflow.expected_argument_exit(
f'value for {attrib}', ', '.join(['true', 'false']),
value)
gs_body[attrib] = value
return
controlflow.invalid_argument_exit(myarg, f'gam {function} group')