Added input_dir directory variable to gam.cfg

Added support for the new resource calendar setting `autoAcceptInvitations`.
# 1865
This commit is contained in:
Ross Scroggs
2025-12-10 17:51:20 -08:00
parent 07b3e89809
commit 185386e5e7
6 changed files with 289 additions and 85 deletions

View File

@@ -1962,6 +1962,7 @@ gam calendar <CalendarEntity> printevents <EventSelectProperty>* <EventDisplayPr
[formatjson [quotechar <Character>]] [todrive <ToDriveAttribute>*]
<CalendarSettingsField> ::=
autoacceptinvitations|
conferenceproperties|
description|
id|
@@ -1971,6 +1972,7 @@ gam calendar <CalendarEntity> printevents <EventSelectProperty>* <EventDisplayPr
<CalendarSettingsFieldList> ::= "<CalendarSettingsField>(,<CalendarSettingsField>)*"
<CalendarSettings> ::=
(autoacceptinvitations [<Boolean>])|
(description <String>)|
(location <String>)|
(summary <String>)|
@@ -1987,10 +1989,10 @@ gam calendar|calendars <CalendarEntity> modify <CalendarSettings>+
# Chat Bot
<ChatContent> ::=
((text <String>)|
(textfile <FileName> [charset <Charset>])|
(gdoc <UserGoogleDoc>)|
(gcsdoc <StorageBucketObjectName>))
(text <String>)|
(textfile <FileName> [charset <Charset>])|
(gdoc <UserGoogleDoc>)|
(gcsdoc <StorageBucketObjectName>)
<ChatMember> ::= spaces/<String>/members/<String>
<ChatMessage> ::= spaces/<String>/messages/<String>
@@ -3201,10 +3203,10 @@ gam course <CourseID> create|add alias <CourseAlias>
gam course <CourseID> delete alias <CourseAlias>
<CourseAnnouncementContent> ::=
((text <String>)|
(textfile <FileName> [charset <Charset>])|
(gdoc <UserGoogleDoc>)|
(gcsdoc <StorageBucketObjectName>))
(text <String>)|
(textfile <FileName> [charset <Charset>])|
(gdoc <UserGoogleDoc>)|
(gcsdoc <StorageBucketObjectName>)
gam course <CourseID> create announcement
<CourseAnnouncementContent> [scheduledtime <Time>] [state draft|published]
@@ -3575,6 +3577,12 @@ gam show domainaliases|aliasdomains
# Domain - Contacts and Global Address List
<ContactNoteContent> ::=
<String>|
(file|textfile <FileName> [charset <Charset>])|
(gdoc <UserGoogleDoc>)
(gcsdoc <StorageBucketObjectName>)
<ContactAttribute> ::=
(additionalname|middlename <String>)|
(address clear|(work|home|other|<String> ((formatted|unstructured <String>)|(streetaddress <String>)|(pobox <String>)|
@@ -3600,7 +3608,7 @@ gam show domainaliases|aliasdomains
(mileage <String>)|
(name <String>)|
(nickname <String>)|
(note <NoteContent>)|
(note <ContactNoteContent>)|
(occupation <String>)|
(organization clear|(work|other|<String> <String> ((location <String>)|(department <String>)|(title <String>)|(jobdescription <String>)|(symbol <String>))* notprimary|primary))|
(phone clear|(work|home|other|fax|work_fax|home_fax|other_fax|main|company_main|assistant|mobile|work_mobile|pager|work_pager|car|radio|callback|isdn|telex|tty_tdd|<String> <String> notprimary|primary))|
@@ -4728,6 +4736,7 @@ gam print resoldsubscriptions [todrive <ToDriveAttribute>*]
(zipcode|postalcode <String>)
<ResourceAttribute> ::=
(autoacceptinvitations [<Boolean>])|
(addfeatures <FeatureNameList>)|
(buildingid <BuildingID>)|
(capacity <Number>)|
@@ -4743,6 +4752,7 @@ gam print resoldsubscriptions [todrive <ToDriveAttribute>*]
<ResourceFieldName> ::=
acls|
autoacceptinvitations|
buildingid|
calendar|
capacity|
@@ -5668,6 +5678,11 @@ gam download storagefile <StorageBucketObjectName>
<UserOrderByFieldName> ::=
familyname|lastname|givenname|firstname|email
<UserNoteContent> ::=
<String>|
(file|textfile|htmlfile <FileName> [charset <Charset>])|
(gdoc|ghtml <UserGoogleDoc>)
<UserBasicAttribute> ::=
(archive|archived <Boolean>)|
(changepassword|changepasswordatnextlogin <Boolean>)|
@@ -5681,9 +5696,7 @@ gam download storagefile <StorageBucketObjectName>
(ipwhitelisted <Boolean>)|
(language clear|<LanguageList>)|
(lastname|familyname <String>)|
(note clear|([text_html|text_plain] <String>|
(file|htmlfile <FileName> [charset <Charset>])|
(gdoc|ghtml <UserGoogleDoc>)))|
(note clear|([text_html|text_plain] <UserNoteContent))|
(ou|org|orgunitpath <OrgUnitPath>|<OrgUnitID>)
(password (random [<Integer>])|(uniquerandom [<Integer>])|
blocklogin|
@@ -6054,6 +6067,7 @@ gam <UserTypeEntity> print backupcodes|verificationcodes [todrive <ToDriveAttrib
(summary <String>)
<CalendarSettings> ::=
(autoacceptinvitations [<Boolean>])|
(description <String>)|
(location <String>)|
(summary <String>)|
@@ -6380,10 +6394,10 @@ gam <UserTypeEntity> print focustime|outofoffice|workinglocation
# Users - Chat
<ChatContent> ::=
((text <String>)|
(textfile <FileName> [charset <Charset>])|
(gdoc <UserGoogleDoc>)|
(gcsdoc <StorageBucketObjectName>))
(text <String>)|
(textfile <FileName> [charset <Charset>])|
(gdoc <UserGoogleDoc>)|
(gcsdoc <StorageBucketObjectName>)
<ChatMember> ::= spaces/<String>/members/<String>
<ChatMemberList> ::= "<ChatMember>(,<ChatMember>)*"
@@ -8071,16 +8085,16 @@ gam <UserTypeEntity> check isinvitable [todrive <ToDriveAttribute>*]
# Users - Keep Notes
<NoteContent> ::=
((text <String>)|
(textfile <FileName> [charset <Charset>])|
(gdoc <UserGoogleDoc>)|
(gcsdoc <StorageBucketObjectName>)|
<JSONData>)
<KeepNoteContent> ::=
(text <String>)|
(textfile <FileName> [charset <Charset>])|
(gdoc <UserGoogleDoc>)|
(gcsdoc <StorageBucketObjectName>)|
<JSONData>
gam <UserTypeEntity> create note [title <String>]
[missingtextvalue <String>]
<NoteContent>
<KeepNoteContent>
[copyacls [copyowneraswriter]]
[compact|formatjson|nodetails]
@@ -8251,12 +8265,17 @@ gam <UserItem> print meettranscripts <MeetConferenceName> [todrive <ToDriveAttri
# Users - Contacts and Profiles
<BiographyContent> ::=
<String>|
(file|textfile <FileName> [charset <Charset>])|
(gdoc <UserGoogleDoc>)
<PeopleContactAttribute> ::=
(additionalname|middlename <String>)|
(address clear|(work|home|other|<String> ((formatted|unstructured <String>)|(streetaddress <String>)|(pobox <String>)|
(neighborhood <String>)|(locality <String>)|(region <String>)|(postalcode <String>)|(country <String>))* notprimary|primary))|
(billinginfo <String>)|
(biography|biographies <String>|(file <FileName> [charset <Charset>])|(gdoc <UserGoogleDoc>))|
(biography|biographies <BiographyContent)|
(birthday <Date>)|
(calendar clear|(work|home|free-busy|<String> <URL> notprimary|primary))|
(clientdata <String> <String>)|

View File

@@ -1,3 +1,11 @@
7.30.00
Added `input_dir` directory variable to `gam.cfg` that is used to select a directory for reading files with non-absolute file names.
The default is an empty string that matches the current behavior where these files are read from the current working directory.
This will be most useful in multiple domain situations where each domain will have distinct `drive_dir` and `input_dir` values.
Added support for the new resource calendar setting `autoAcceptInvitations`.
7.29.04
Updated `gam delete chromepolicy chrome.users.apps.InstallType ou <OrgUnitItem> appid <AppID>`

View File

@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
"""
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
__version__ = '7.29.04'
__version__ = '7.30.00'
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
#pylint: disable=wrong-import-position
@@ -1856,7 +1856,7 @@ def getStringOrFile(myarg, minLen=0, unescapeCRLF=False):
if myarg in {'file', 'textfile', 'htmlfile'}:
filename = getString(Cmd.OB_FILE_NAME)
encoding = getCharSet()
return (readFile(filename, encoding=encoding), encoding, html)
return (readFile(setFilePath(filename, GC.INPUT_DIR), encoding=encoding), encoding, html)
if myarg in {'gdoc', 'ghtml'}:
f = getGDocData(myarg)
data = f.read()
@@ -2254,7 +2254,7 @@ def getJSON(deleteFields):
filename = getString(Cmd.OB_FILE_NAME)
encoding = getCharSet()
try:
jsonData = json.loads(readFile(filename, encoding=encoding))
jsonData = json.loads(readFile(setFilePath(filename, GC.INPUT_DIR), encoding=encoding))
except (IndexError, KeyError, SyntaxError, TypeError, ValueError) as e:
Cmd.Backup()
usageErrorExit(Msg.JSON_ERROR.format(str(e), filename))
@@ -2906,14 +2906,13 @@ def entityModifierNewValueKeyValueActionPerformed(entityValueList, modifier, new
def cleanFilename(filename):
return sanitize_filename(filename, '_')
def setFilePath(fileName):
if fileName.startswith('./') or fileName.startswith('.\\'):
fileName = os.path.join(os.getcwd(), fileName[2:])
else:
fileName = os.path.expanduser(fileName)
if not os.path.isabs(fileName):
fileName = os.path.join(GC.Values[GC.DRIVE_DIR], fileName)
return fileName
def setFilePath(filename, cfgDir):
if filename.startswith('./') or filename.startswith('.\\'):
return os.path.join(os.getcwd(), filename[2:])
filename = os.path.expanduser(filename)
if os.path.isabs(filename):
return filename
return os.path.join(GC.Values[cfgDir], filename)
def uniqueFilename(targetFolder, filetitle, overwrite, extension=None):
filename = filetitle
@@ -3278,6 +3277,7 @@ def openCSVFileReader(filename, fieldnames=None):
getCharSet()
else:
encoding = getCharSet()
filename = setFilePath(filename, GC.INPUT_DIR)
f = openFile(filename, mode=DEFAULT_CSV_READ_MODE, encoding=encoding)
if checkArgumentPresent('warnifnodata'):
loc = f.tell()
@@ -3888,7 +3888,7 @@ def SetGlobalVariables():
def _setCSVFile(fileName, mode, encoding, writeHeader, multi):
if fileName != '-':
fileName = setFilePath(fileName)
fileName = setFilePath(fileName, GC.DRIVE_DIR)
GM.Globals[GM.CSVFILE][GM.REDIRECT_NAME] = fileName
GM.Globals[GM.CSVFILE][GM.REDIRECT_MODE] = mode
GM.Globals[GM.CSVFILE][GM.REDIRECT_ENCODING] = encoding
@@ -3909,7 +3909,7 @@ def SetGlobalVariables():
else:
GM.Globals[stdtype][GM.REDIRECT_FD] = os.fdopen(os.dup(sys.stderr.fileno()), mode, encoding=GM.Globals[GM.SYS_ENCODING])
else:
fileName = setFilePath(fileName)
fileName = setFilePath(fileName, GC.DRIVE_DIR)
if multi and mode == DEFAULT_FILE_WRITE_MODE:
deleteFile(fileName)
mode = DEFAULT_FILE_APPEND_MODE
@@ -6815,6 +6815,7 @@ def getEntitiesFromFile(shlexSplit, returnSet=False):
filenameLower = filename.lower()
if filenameLower not in {'gcsv', 'gdoc', 'gcscsv', 'gcsdoc'}:
encoding = getCharSet()
filename = setFilePath(filename, GC.INPUT_DIR)
f = openFile(filename, encoding=encoding, stripUTFBOM=True)
elif filenameLower in {'gcsv', 'gdoc'}:
f = getGDocData(filenameLower)
@@ -7238,7 +7239,7 @@ def checkUserSuspended(cd, user, entityType=Ent.USER, i=0, count=0):
def _addAttachmentsToMessage(message, attachments):
for attachment in attachments:
try:
attachFilename = attachment[0]
attachFilename = setFilePath(attachment[0], GC.INPUT_DIR)
attachContentType, attachEncoding = mimetypes.guess_type(attachFilename)
if attachContentType is None or attachEncoding is not None:
attachContentType = 'application/octet-stream'
@@ -7263,7 +7264,7 @@ def _addAttachmentsToMessage(message, attachments):
def _addEmbeddedImagesToMessage(message, embeddedImages):
for embeddedImage in embeddedImages:
try:
imageFilename = embeddedImage[0]
imageFilename = setFilePath(embeddedImage[0], GC.INPUT_DIR)
imageContentType, imageEncoding = mimetypes.guess_type(imageFilename)
if imageContentType is None or imageEncoding is not None:
imageContentType = 'application/octet-stream'
@@ -10270,6 +10271,7 @@ def doBatch(threadBatch=False):
filenameLower = filename.lower()
if filenameLower not in {'gdoc', 'gcsdoc'}:
encoding = getCharSet()
filename = setFilePath(filename, GC.INPUT_DIR)
f = openFile(filename, encoding=encoding, stripUTFBOM=True)
elif filenameLower == 'gdoc':
f = getGDocData(filenameLower)
@@ -18500,7 +18502,7 @@ def doCheckOrgUnit():
else:
invalidChoiceExit(field, list(ORG_ITEMS_FIELD_MAP), True)
elif myarg == 'filename':
fileName = setFilePath(getString(Cmd.OB_FILE_NAME))
fileName = setFilePath(getString(Cmd.OB_FILE_NAME), GC.DRIVE_DIR)
elif myarg == 'movetoou':
movetoouLocation = Cmd.Location()
status, moveToOrgUnitPath, _ = checkOrgUnitPathExists(cd, getOrgUnitItem())
@@ -18517,7 +18519,7 @@ def doCheckOrgUnit():
usageErrorExit(Msg.OU_AND_MOVETOOU_CANNOT_BE_IDENTICAL.format(orgUnitPath, moveToOrgUnitPath))
if 'subous' in fieldsList and moveToOrgUnitPathLower.startswith(orgUnitPathLower):
usageErrorExit(Msg.OU_SUBOUS_CANNOT_BE_MOVED_TO_MOVETOOU.format(orgUnitPath, moveToOrgUnitPath))
fileName = setFilePath(fileName)
fileName = setFilePath(fileName, GC.DRIVE_DIR)
f = openFile(fileName, DEFAULT_FILE_WRITE_MODE)
orgUnitItemCounts = {}
for field in sorted(fieldsList):
@@ -29136,7 +29138,7 @@ def doDeleteChromePolicy():
policy = result['resolvedPolicies'][0]
if request['policyTargetKey']['targetResource'] == policy['addedSourceKey'].get('targetResource', ''):
request['policySchema'] = 'chrome.users.apps.*'
except (GAPI.notFound, GAPI.permissionDenied, GAPI.invalidArgument, GAPI.serviceNotAvailable, GAPI.quotaExceeded) as e:
except (GAPI.notFound, GAPI.permissionDenied, GAPI.invalidArgument, GAPI.serviceNotAvailable, GAPI.quotaExceeded):
continue
try:
callGAPI(service, function,
@@ -37107,6 +37109,134 @@ def _checkPoliciesWithDASA():
systemErrorExit(USAGE_ERROR_RC,
Msg.COMMAND_NOT_COMPATIBLE_WITH_ENABLE_DASA.format(Act.ToPerform().lower(), Cmd.ARG_CIPOLICIES))
def _getCIPolicyOrgUnitTarget(cd, myarg, groupEmail):
if groupEmail:
Cmd.Backup()
usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'group'))
targetName, targetResource = _getOrgunitsOrgUnitIdPath(cd, getString(Cmd.OB_ORGUNIT_PATH))
return (targetName, targetResource)
def _getCIPolicyGroupTarget(cd, myarg, orgUnit):
if orgUnit:
Cmd.Backup()
usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'ou|org|orgunit'))
targetName = getEmailAddress(returnUIDprefix='uid:')
targetResource = f"groups/{convertEmailAddressToUID(targetName, cd, emailType='group')}"
return (targetName, targetResource)
# gam create policy
# json <JSONData>
# [(ou|orgunit <OrgUnitItem>)|(group <GroupItem>)]
# gam update policy
# json <JSONData>
# [(ou|orgunit <OrgUnitItem>)|(group <GroupItem>)]
def doCreateUpdateCIPolicy():
_checkPoliciesWithDASA()
ci = buildGAPIObject(API.CLOUDIDENTITY_POLICY_BETA)
cd = buildGAPIObject(API.DIRECTORY)
updateCmd = Act.Get() == Act.UPDATE
groupEmail = orgUnit = None
checkArgumentPresent('json', True)
jsonData = getJSON(['type'])
if updateCmd:
pname = jsonData.pop('name', None)
else:
pname = 'New Policy'
if 'policyQuery' in jsonData:
jsonData['policyQuery'].pop('orgUnitPath', None)
jsonData['policyQuery'].pop('groupEmail', None)
jsonData['policyQuery'].pop('sortOrder', None)
if 'setting' in jsonData and 'value' in jsonData['setting']:
jsonData['setting']['value'].pop('createTime', None)
jsonData['setting']['value'].pop('updateTime', None)
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if myarg in {'ou', 'org', 'orgunit'}:
orgUnit, targetResource = _getCIPolicyOrgUnitTarget(cd, myarg, groupEmail)
jsonData.setdefault('policyQuery', {})
jsonData['policyQuery'].pop('group', None)
jsonData['policyQuery']['orgUnit'] = targetResource
elif myarg == 'group':
groupEmail, targetResource = _getCIPolicyGroupTarget(cd, myarg, orgUnit)
jsonData.setdefault('policyQuery', {})
jsonData['policyQuery'].pop('orgUnit', None)
jsonData['policyQuery']['group'] = targetResource
else:
unknownArgumentExit()
jsonData['customer'] = _getCustomersCustomerIdWithC()
try:
if updateCmd:
result = callGAPI(ci.policies(), 'patch',
bailOnInternalError=True,
throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
name=pname, body=jsonData)
else:
result = callGAPI(ci.policies(), 'create',
bailOnInternalError=True,
throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
body=jsonData)
if result['done']:
if 'error' not in result:
if not updateCmd:
pname = result['response'].get('id', pname)
entityActionPerformed([Ent.POLICY, pname])
else:
entityActionFailedWarning([Ent.POLICY, pname], result['error']['message'])
else:
entityActionPerformedMessage([Ent.POLICY, pname], Msg.ACTION_IN_PROGRESS.format('delete'))
except (GAPI.invalid, GAPI.invalidArgument, GAPI.permissionDenied, GAPI.internalError) as e:
entityActionFailedWarning([Ent.POLICY, pname], str(e))
# gam delete policies <CIPolicyNameEntity>
def doDeleteCIPolicies():
_checkPoliciesWithDASA()
ci = buildGAPIObject(API.CLOUDIDENTITY_POLICY_BETA)
entityList = getEntityList(Cmd.OB_CIPOLICY_NAME_ENTITY)
checkForExtraneousArguments()
i = 0
count = len(entityList)
for pname in entityList:
i += 1
if pname.startswith('policies/'):
try:
policies = [callGAPI(ci.policies(), 'get',
bailOnInternalError=True,
throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
name=pname,
fields='name')]
except (GAPI.invalid, GAPI.invalidArgument, GAPI.permissionDenied, GAPI.internalError) as e:
entityActionFailedWarning([Ent.POLICY, pname], str(e), i, count)
continue
else:
if pname.startswith('settings/'):
pname = pname.split('/')[1]
ifilter = f"setting.type.matches('{pname}')"
printGettingAllAccountEntities(Ent.POLICY, ifilter)
policies = _filterPolicies(ci, getPageMessage(), ifilter)
jcount = len(policies)
performActionNumItems(jcount, Ent.POLICY)
Ind.Increment()
j = 0
for policy in policies:
j += 1
pname = policy['name']
try:
result = callGAPI(ci.policies(), 'delete',
bailOnInternalError=True,
throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
name=pname)
if result['done']:
if 'error' not in result:
entityActionPerformed([Ent.POLICY, pname], j, jcount)
else:
entityActionFailedWarning([Ent.POLICY, pname], result['error']['message'], j, jcount)
else:
entityActionPerformedMessage([Ent.POLICY, pname], Msg.ACTION_IN_PROGRESS.format('delete'), j, jcount)
except (GAPI.invalid, GAPI.invalidArgument, GAPI.permissionDenied, GAPI.internalError) as e:
entityActionFailedWarning([Ent.POLICY, pname], str(e), j, jcount)
Ind.Decrement()
# gam info policies <CIPolicyNameEntity>
# [nowarnings] [noappnames]
# [formatjson]
@@ -38851,6 +38981,7 @@ RESOURCE_CATEGORY_MAP = {
}
def _getResourceCalendarAttributes(cd, body, updateMode):
autoAcceptInvitations = None
featureChanges = {'add': set(), 'remove': set()}
while Cmd.ArgumentsRemaining():
myarg = getArgument()
@@ -38887,26 +39018,45 @@ def _getResourceCalendarAttributes(cd, body, updateMode):
body['resourceCategory'] = getChoice(RESOURCE_CATEGORY_MAP, mapChoice=True)
elif myarg in {'userdescription', 'uservisibledescription'}:
body['userVisibleDescription'] = getString(Cmd.OB_STRING)
elif myarg == 'autoacceptinvitations':
autoAcceptInvitations = getBoolean()
else:
unknownArgumentExit()
if ('featureInstances' in body and not body['featureInstances'] and
not featureChanges['add'] and not featureChanges['remove']):
body['featureInstances'] = [{}]
if not updateMode:
return body
return body, featureChanges
return body, autoAcceptInvitations, None
return body, autoAcceptInvitations, featureChanges
def updateAutoAcceptInvitations(cal, calId, autoAcceptInvitations, i=0, count=0):
Ind.Increment()
try:
callGAPI(cal.calendars(), 'patch',
throwReasons=GAPI.CALENDAR_THROW_REASONS+[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.INVALID],
calendarId=calId, body={'autoAcceptInvitations': autoAcceptInvitations})
entityActionPerformed([Ent.CALENDAR, calId], i, count)
except (GAPI.notFound, GAPI.forbidden, GAPI.invalid) as e:
entityActionFailedWarning([Ent.CALENDAR, calId], str(e), i, count)
except GAPI.notACalendarUser:
userCalServiceNotEnabledWarning(calId, i, count)
Ind.Decrement()
# gam create resource <ResourceID> <Name> <ResourceAttribute>*
def doCreateResourceCalendar():
cd = buildGAPIObject(API.DIRECTORY)
body = _getResourceCalendarAttributes(cd, {'resourceId': getString(Cmd.OB_RESOURCE_ID), 'resourceName': getString(Cmd.OB_NAME)}, False)
body, autoAcceptInvitations, _ = _getResourceCalendarAttributes(cd, {'resourceId': getString(Cmd.OB_RESOURCE_ID), 'resourceName': getString(Cmd.OB_NAME)}, False)
if autoAcceptInvitations is not None:
cal = buildGAPIObject(API.CALENDAR)
try:
callGAPI(cd.resources().calendars(), 'insert',
throwReasons=[GAPI.INVALID, GAPI.INVALID_INPUT, GAPI.SERVICE_NOT_AVAILABLE,
GAPI.REQUIRED, GAPI.DUPLICATE, GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
customer=GC.Values[GC.CUSTOMER_ID], body=body, fields='')
result = callGAPI(cd.resources().calendars(), 'insert',
throwReasons=[GAPI.INVALID, GAPI.INVALID_INPUT, GAPI.SERVICE_NOT_AVAILABLE,
GAPI.REQUIRED, GAPI.DUPLICATE, GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
customer=GC.Values[GC.CUSTOMER_ID], body=body, fields='resourceEmail')
entityActionPerformed([Ent.RESOURCE_CALENDAR, body['resourceId']])
if autoAcceptInvitations is not None:
updateAutoAcceptInvitations(cal, result['resourceEmail'], autoAcceptInvitations)
except (GAPI.invalid, GAPI.invalidInput, GAPI.serviceNotAvailable) as e:
entityActionFailedWarning([Ent.RESOURCE_CALENDAR, body['resourceId']], str(e))
except GAPI.required as e:
@@ -38924,17 +39074,19 @@ def doCreateResourceCalendar():
def _doUpdateResourceCalendars(entityList):
cd = buildGAPIObject(API.DIRECTORY)
body, featureChanges = _getResourceCalendarAttributes(cd, {}, True)
body, autoAcceptInvitations, featureChanges = _getResourceCalendarAttributes(cd, {}, True)
if autoAcceptInvitations is not None:
cal = buildGAPIObject(API.CALENDAR)
i = 0
count = len(entityList)
for resourceId in entityList:
i += 1
try:
if featureChanges['add'] or featureChanges['remove']:
features = callGAPI(cd.resources().calendars(), 'get',
throwReasons=[GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.FORBIDDEN],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
customer=GC.Values[GC.CUSTOMER_ID], calendarResourceId=resourceId, fields='featureInstances(feature(name))')
if autoAcceptInvitations is not None or featureChanges['add'] or featureChanges['remove']:
result = callGAPI(cd.resources().calendars(), 'get',
throwReasons=[GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.FORBIDDEN],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
customer=GC.Values[GC.CUSTOMER_ID], calendarResourceId=resourceId, fields='resourceEmail,featureInstances(feature(name))')
bodyFeatures = body.pop('featureInstances', [])
body['featureInstances'] = []
featureSet = set()
@@ -38943,7 +39095,7 @@ def _doUpdateResourceCalendars(entityList):
if featureName not in featureChanges['remove'] and featureName not in featureSet:
body['featureInstances'].append({'feature': {'name': featureName}})
featureSet.add(featureName)
for feature in features.get('featureInstances', []):
for feature in result.get('featureInstances', []):
featureName = feature['feature']['name']
if featureName not in featureChanges['remove'] and featureName not in featureSet:
body['featureInstances'].append({'feature': {'name': featureName}})
@@ -38960,6 +39112,8 @@ def _doUpdateResourceCalendars(entityList):
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
customer=GC.Values[GC.CUSTOMER_ID], calendarResourceId=resourceId, body=body, fields='')
entityActionPerformed([Ent.RESOURCE_CALENDAR, resourceId], i, count)
if autoAcceptInvitations is not None:
updateAutoAcceptInvitations(cal, result['resourceEmail'], autoAcceptInvitations, i, count)
except (GAPI.invalid, GAPI.invalidInput, GAPI.serviceNotAvailable, GAPI.required) as e:
entityActionFailedWarning([Ent.RESOURCE_CALENDAR, resourceId], str(e), i, count)
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden):
@@ -41171,6 +41325,7 @@ def doCalendarsPrintShowEvents(calIds):
# <CalendarSettings> ::==
# [description <String>] [location <String>] [summary <String>] [timezone <TimeZone>]
# [autoacceptinvitations [<Boolean>]]
def _getCalendarSetting(myarg, body):
if myarg == 'description':
body['description'] = getStringWithCRsNLs()
@@ -41180,6 +41335,8 @@ def _getCalendarSetting(myarg, body):
body['summary'] = getString(Cmd.OB_STRING)
elif myarg == 'timezone':
body['timeZone'] = getString(Cmd.OB_STRING)
elif myarg == 'autoacceptinvitations':
body['autoAcceptInvitations'] = getBoolean()
else:
return False
return True
@@ -41232,9 +41389,12 @@ def _showCalendarSettings(calendar, j, jcount):
Ind.Increment()
printKeyValueList(['AllowedConferenceSolutionTypes', ','.join(calendar.get('conferenceProperties', {}).get('allowedConferenceSolutionTypes', []))])
Ind.Decrement()
if 'autoAcceptInvitations' in calendar:
printKeyValueList(['AutoAcceptInvitations', calendar['autoAcceptInvitations']])
Ind.Decrement()
CALENDAR_SETTINGS_FIELDS_CHOICE_MAP = {
'autoacceptinvitations': 'autoAcceptInvitations',
'conferenceproperties': 'conferenceProperties',
'dataowner': 'dataOwner',
'description': 'description',
@@ -41720,6 +41880,26 @@ def _copyStorageObjects(objects, target_bucket, target_prefix):
ClientAPIAccessDeniedExit()
Act.Set(action)
def md5MatchesFile(filename, expected_md5, j=0, jcount=0):
action = Act.Get()
Act.Set(Act.VERIFY)
try:
f = openFile(filename, 'rb')
hash_md5 = hashlib.md5()
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
closeFile(f)
actual_hash = hash_md5.hexdigest()
if actual_hash == expected_md5:
entityActionPerformed([Ent.FILE, filename, Ent.MD5HASH, expected_md5], j, jcount)
Act.Set(action)
return True
entityActionFailedWarning([Ent.FILE, filename, Ent.MD5HASH, expected_md5], Msg.DOES_NOT_MATCH.format(actual_hash), j, jcount)
Act.Set(action)
return False
except IOError as e:
systemErrorExit(FILE_ERROR_RC, fileErrorMessage(filename, e))
def _getCloudStorageObject(s, bucket, s_object, localFilename, expectedMd5=None, zipToStdout=False, j=0, jcount=0):
if not zipToStdout:
localFilename = cleanFilepath(localFilename)
@@ -42550,26 +42730,6 @@ def doPrintShowVaultExports():
if csvPF:
csvPF.writeCSVfile('Vault Exports')
def md5MatchesFile(filename, expected_md5, j=0, jcount=0):
action = Act.Get()
Act.Set(Act.VERIFY)
try:
f = openFile(filename, 'rb')
hash_md5 = hashlib.md5()
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
closeFile(f)
actual_hash = hash_md5.hexdigest()
if actual_hash == expected_md5:
entityActionPerformed([Ent.FILE, filename, Ent.MD5HASH, expected_md5], j, jcount)
Act.Set(action)
return True
entityActionFailedWarning([Ent.FILE, filename, Ent.MD5HASH, expected_md5], Msg.DOES_NOT_MATCH.format(actual_hash), j, jcount)
Act.Set(action)
return False
except IOError as e:
systemErrorExit(FILE_ERROR_RC, fileErrorMessage(filename, e))
# gam copy vaultexport|export <ExportItem> matter <MatterItem>
# [targetbucket <String>] [targetprefix <String>]
# [bucketmatchpattern <REMatchPattern>] [objectmatchpattern <REMatchPattern>]
@@ -47372,7 +47532,7 @@ def doCreateInboundSSOCredential():
if not profile:
return
elif myarg == 'pemfile':
pemData = readFile(getString(Cmd.OB_FILE_NAME))
pemData = readFile(setFilePath(getString(Cmd.OB_FILE_NAME), GC.INPUT_DIR))
elif myarg == 'generatekey':
generateKey = True
elif myarg == 'replaceoldest':
@@ -73211,6 +73371,7 @@ def _draftImportInsertMessage(users, operation):
filename = getString(Cmd.OB_FILE_NAME)
if checkArgumentPresent('charset'):
emlEncoding = getString(Cmd.OB_CHAR_SET)
filename = setFilePath(filename, GC.INPUT_DIR)
msgText = readFile(filename, encoding=emlEncoding)
emlFile = True
internalDateSource = 'dateHeader'
@@ -76042,8 +76203,7 @@ def createSmime(users):
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if myarg == 'file':
smimefile = getString(Cmd.OB_FILE_NAME)
smimeData = readFile(smimefile, mode='rb')
smimeData = readFile(setFilePath(getString(Cmd.OB_FILE_NAME), GC.INPUT_DIR), mode='rb')
body['pkcs12'] = base64.urlsafe_b64encode(smimeData).decode(UTF8)
elif myarg == 'password':
body['encryptedKeyPassword'] = getString(Cmd.OB_PASSWORD)
@@ -78124,7 +78284,7 @@ def importTasklist(users):
filename = getString(Cmd.OB_FILE_NAME)
encoding = getCharSet()
try:
jsonData = json.loads(readFile(filename, encoding=encoding))
jsonData = json.loads(readFile(setFilePath(filename, GC.INPUT_DIR), encoding=encoding))
except (IndexError, KeyError, SyntaxError, TypeError, ValueError) as e:
Cmd.Backup()
usageErrorExit(Msg.JSON_ERROR.format(str(e), filename))
@@ -78648,6 +78808,7 @@ MAIN_ADD_CREATE_FUNCTIONS = {
Cmd.ARG_CHROMEPOLICYIMAGE: doCreateChromePolicyImage,
Cmd.ARG_CHROMEPROFILECOMMAND: doCreateChromeProfileCommand,
Cmd.ARG_CIGROUP: doCreateCIGroup,
Cmd.ARG_CIPOLICY: doCreateUpdateCIPolicy,
Cmd.ARG_CONTACT: doCreateDomainContact,
Cmd.ARG_COURSE: doCreateCourse,
Cmd.ARG_COURSESTUDENTGROUP: doCreateCourseStudentGroups,
@@ -78768,6 +78929,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
Cmd.ARG_CHROMEPOLICY: doDeleteChromePolicy,
Cmd.ARG_CHROMEPROFILE: doDeleteChromeProfile,
Cmd.ARG_CIGROUP: doDeleteCIGroups,
Cmd.ARG_CIPOLICY: doDeleteCIPolicies,
Cmd.ARG_CLASSROOMINVITATION: doDeleteClassroomInvitations,
Cmd.ARG_CONTACT: doDeleteDomainContacts,
Cmd.ARG_CONTACTPHOTO: doDeleteDomainContactPhoto,
@@ -79161,6 +79323,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
Cmd.ARG_CHATMESSAGE: doUpdateChatMessage,
Cmd.ARG_CHROMEPOLICY: doUpdateChromePolicy,
Cmd.ARG_CIGROUP: doUpdateCIGroups,
Cmd.ARG_CIPOLICY: doCreateUpdateCIPolicy,
Cmd.ARG_CONTACT: doUpdateDomainContacts,
Cmd.ARG_CONTACTPHOTO: doUpdateDomainContactPhoto,
Cmd.ARG_COURSE: doUpdateCourse,

View File

@@ -50,6 +50,7 @@ CLOUDIDENTITY_INBOUND_SSO = 'cloudidentityinboundsso'
CLOUDIDENTITY_ORGUNITS = 'cloudidentityorgunits'
CLOUDIDENTITY_ORGUNITS_BETA = 'cloudidentityorgunitsbeta'
CLOUDIDENTITY_POLICY = 'cloudidentitypolicy'
CLOUDIDENTITY_POLICY_BETA = 'cloudidentitypolicybeta'
CLOUDIDENTITY_USERINVITATIONS = 'cloudidentityuserinvitations'
CLOUDRESOURCEMANAGER = 'cloudresourcemanager'
CONTACTS = 'contacts'
@@ -245,6 +246,7 @@ _INFO = {
CLOUDIDENTITY_ORGUNITS: {'name': 'Cloud Identity API - OrgUnits', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
CLOUDIDENTITY_ORGUNITS_BETA: {'name': 'Cloud Identity API - OrgUnits Beta', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
CLOUDIDENTITY_POLICY: {'name': 'Cloud Identity API - Policy', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
CLOUDIDENTITY_POLICY_BETA: {'name': 'Cloud Identity API - Policy Beta', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
CLOUDIDENTITY_USERINVITATIONS: {'name': 'Cloud Identity API - User Invitations', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
CLOUDRESOURCEMANAGER: {'name': 'Cloud Resource Manager API v3', 'version': 'v3', 'v2discovery': True},
CONTACTS: {'name': 'Contacts API', 'version': 'v3', 'v2discovery': False},
@@ -398,6 +400,11 @@ _CLIENT_SCOPES = [
'subscopes': READONLY,
'roByDefault': True,
'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
{'name': 'Cloud Identity API - Policy Beta',
'api': CLOUDIDENTITY_POLICY_BETA,
'subscopes': [],
'offByDefault': True,
'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'},
{'name': 'Cloud Identity API - User Invitations',
'api': CLOUDIDENTITY_USERINVITATIONS,
'subscopes': READONLY,

View File

@@ -157,7 +157,7 @@ DEVELOPER_PREVIEW_API_KEY = 'developer_preview_api_key'
DEVICE_MAX_RESULTS = 'device_max_results'
# Domain obtained from gam.cfg or oauth2.txt
DOMAIN = 'domain'
# Google Drive download directory
# directory for file output
DRIVE_DIR = 'drive_dir'
# When retrieving lists of Drive files/folders from API, how many should be retrieved in each chunk
DRIVE_MAX_RESULTS = 'drive_max_results'
@@ -177,6 +177,8 @@ EXTRA_ARGS = 'extra_args'
GMAIL_CSE_INCERT_DIR = 'gmail_cse_incert_dir'
# Gmail CSE KACL wrapped key files
GMAIL_CSE_INKEY_DIR = 'gmail_cse_inkey_dir'
# directory for file input
INPUT_DIR = 'input_dir'
# When processing items in batches, how many seconds should GAM wait between batches
INTER_BATCH_WAIT = 'inter_batch_wait'
# When retrieving lists of licenses from API, how many should be retrieved in each chunk
@@ -394,6 +396,7 @@ Defaults = {
EXTRA_ARGS: '',
GMAIL_CSE_INCERT_DIR: '',
GMAIL_CSE_INKEY_DIR: '',
INPUT_DIR: '',
INTER_BATCH_WAIT: '0',
LICENSE_MAX_RESULTS: '100',
LICENSE_SKUS: '',
@@ -564,6 +567,7 @@ VAR_INFO = {
EXTRA_ARGS: {VAR_TYPE: TYPE_FILE, VAR_SIGFILE: FN_EXTRA_ARGS_TXT, VAR_SFFT: ('', FN_EXTRA_ARGS_TXT), VAR_ACCESS: os.R_OK},
GMAIL_CSE_INCERT_DIR: {VAR_TYPE: TYPE_DIRECTORY},
GMAIL_CSE_INKEY_DIR: {VAR_TYPE: TYPE_DIRECTORY},
INPUT_DIR: {VAR_TYPE: TYPE_DIRECTORY},
INTER_BATCH_WAIT: {VAR_TYPE: TYPE_FLOAT, VAR_LIMITS: (0.0, 60.0)},
LICENSE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (10, 1000)},
LICENSE_SKUS: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},

View File

@@ -231,6 +231,11 @@ When specifying a `<UserMultiAttribute>` you have to specify all instances; ther
You can remove all instances of a `<UserMultiAttribute>` with `<UserClearAttribute>`.
```
<UserNoteContent> ::=
<String>|
(file|textfile|htmlfile <FileName> [charset <Charset>])|
(gdoc|ghtml <UserGoogleDoc>)
<UserBasicAttribute> ::=
(archive|archived <Boolean>)|
(changepassword|changepasswordatnextlogin <Boolean>)|
@@ -244,9 +249,7 @@ You can remove all instances of a `<UserMultiAttribute>` with `<UserClearAttribu
(ipwhitelisted <Boolean>)|
(language clear|<LanguageList>)|
(lastname|familyname <String>)|
(note clear|([text_html|text_plain] <String>|
(file|htmlfile <FileName> [charset <Charset>])|
(gdoc|ghtml <UserGoogleDoc>)))|
(note clear|([text_html|text_plain] <UserNoteContent>))|
(org|ou|orgunitpath <OrgUnitPath>|<OrgUnitID>)
(password (random [<Integer>])|(uniquerandom [<Integer>])|
blocklogin|