Multiple updates

This commit is contained in:
Ross Scroggs
2024-01-31 10:41:36 -08:00
parent a404af0582
commit 25cdf2e544
4 changed files with 169 additions and 57 deletions

View File

@@ -2,6 +2,43 @@
Merged GAM-Team version Merged GAM-Team version
6.67.31
Updated `gam <UserTypeEntity> claim|transfer ownership <DriveFileEntity>` to properly
handle the case where `<DriveFileEntity>` referencess a Drive shortcut.
6.67.30
Fixed bug where the `fullpath` option in various commands was not converting the generic shared drive name `Drive` to the drive's actual name.
6.67.29
Added optional argument `owneraccess` to `gam courses <CourseEntity> remove teachers|students [owneracccess] <UserTypeEntity` and
`gam course <CourseID> remove teacher|student [owneraccess] <EmailAddress>` in order to test a possible API change.
Updated code to avoid a trap when `gam config auto_batch_min 1 csv file.csv gam ...` was entered.
The `config auto_batch_min 1` is not appropriate in this context and will be ignored.
6.67.28
Improved handling of `Bad Request` error in `gam <UserTypeEntity> collect orphans`.
6.67.27
Updated `gam <UserTypeEntity> collect orphans` to handle the following error:
```
ERROR: 400: badRequest - Bad Request
```
6.67.26
Fixed bug in `gam print vaultexports ... formatjson` that caused a trap.
6.67.25
Added option `owneraccess` to `gam info courses <CourseEntity>` and `gam info course <CourseID>` in order
to test a possible API change.
6.67.24 6.67.24
Fixed bug that caused HTML password notification email messages to be displayed in raw form. Fixed bug that caused HTML password notification email messages to be displayed in raw form.

View File

@@ -246,6 +246,7 @@ FILENAME_SAFE_CHARS = ALPHANUMERIC_CHARS+'-_.() '
CHAT_MESSAGEID_CHARS = string.ascii_lowercase+string.digits+'-' CHAT_MESSAGEID_CHARS = string.ascii_lowercase+string.digits+'-'
ADMIN_ACCESS_OPTIONS = {'adminaccess', 'asadmin'} ADMIN_ACCESS_OPTIONS = {'adminaccess', 'asadmin'}
OWNER_ACCESS_OPTIONS = {'owneraccess', 'asowner'}
# Python 3 values # Python 3 values
DEFAULT_CSV_READ_MODE = 'r' DEFAULT_CSV_READ_MODE = 'r'
@@ -9503,6 +9504,7 @@ def ProcessGAMCommandMulti(pid, numItems, logCmd, mpQueueCSVFile, mpQueueStdout,
GM.Globals[GM.PRINT_CROS_OUS_AND_CHILDREN] = printCrosOUsAndChildren GM.Globals[GM.PRINT_CROS_OUS_AND_CHILDREN] = printCrosOUsAndChildren
GM.Globals[GM.SAVED_STDOUT] = None GM.Globals[GM.SAVED_STDOUT] = None
GM.Globals[GM.SYSEXITRC] = 0 GM.Globals[GM.SYSEXITRC] = 0
GM.Globals[GM.PARSER] = None
if mpQueueCSVFile: if mpQueueCSVFile:
GM.Globals[GM.CSVFILE][GM.REDIRECT_QUEUE] = mpQueueCSVFile GM.Globals[GM.CSVFILE][GM.REDIRECT_QUEUE] = mpQueueCSVFile
if mpQueueStdout: if mpQueueStdout:
@@ -29869,7 +29871,7 @@ def doCreateGroup(ciGroupsAPI=False):
except GAPI.notFound: except GAPI.notFound:
entityActionFailedWarning([entityType, groupEmail], Msg.DOES_NOT_EXIST) entityActionFailedWarning([entityType, groupEmail], Msg.DOES_NOT_EXIST)
except (GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden, except (GAPI.groupNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
GAPI.backendError, GAPI.invalid, GAPI.invalidAttributeValue, GAPI.invalidInput, GAPI.invalidArgument, GAPI.backendError, GAPI.invalid, GAPI.invalidAttributeValue, GAPI.invalidInput, GAPI.invalidArgument, GAPI.failedPrecondition,
GAPI.badRequest, GAPI.permissionDenied, GAPI.systemError, GAPI.serviceLimit, GAPI.serviceNotAvailable, GAPI.authError) as e: GAPI.badRequest, GAPI.permissionDenied, GAPI.systemError, GAPI.serviceLimit, GAPI.serviceNotAvailable, GAPI.authError) as e:
entityActionFailedWarning([entityType, groupEmail], str(e)) entityActionFailedWarning([entityType, groupEmail], str(e))
except GAPI.required: except GAPI.required:
@@ -39731,7 +39733,7 @@ def doPrintShowVaultMatters():
if not FJQC.formatJSON: if not FJQC.formatJSON:
csvPF.WriteRowTitles(row) csvPF.WriteRowTitles(row)
elif csvPF.CheckRowTitles(row): elif csvPF.CheckRowTitles(row):
csvPF.WriteRowNoFilter({'matterId': matter['id'], 'name': matter['name'], csvPF.WriteRowNoFilter({'matterId': matter['matterId'], 'name': matter['name'],
'JSON': json.dumps(cleanJSON(matter), ensure_ascii=False, sort_keys=True)}) 'JSON': json.dumps(cleanJSON(matter), ensure_ascii=False, sort_keys=True)})
if csvPF: if csvPF:
csvPF.writeCSVfile('Vault Matters') csvPF.writeCSVfile('Vault Matters')
@@ -44299,7 +44301,7 @@ class CourseAttributes():
else: else:
return True return True
if self.members != 'none': if self.members != 'none':
_, self.teachers, self.students = _getCourseAliasesMembers(self.croom, self.courseId, {'members': self.members}, _, self.teachers, self.students = _getCourseAliasesMembers(self.croom, self.croom, self.courseId, {'members': self.members},
'nextPageToken,teachers(profile(emailAddress,id))', 'nextPageToken,teachers(profile(emailAddress,id))',
'nextPageToken,students(profile(emailAddress))') 'nextPageToken,students(profile(emailAddress))')
if self.announcementStates: if self.announcementStates:
@@ -44930,7 +44932,7 @@ def _convertCourseUserIdToEmail(croom, userId, emails, entityValueList, i, count
emails[userId] = userEmail emails[userId] = userEmail
return userEmail return userEmail
def _getCourseAliasesMembers(croom, courseId, courseShowProperties, teachersFields, studentsFields, showGettings=False, i=0, count=0): def _getCourseAliasesMembers(croom, ocroom, courseId, courseShowProperties, teachersFields, studentsFields, showGettings=False, i=0, count=0):
aliases = [] aliases = []
teachers = [] teachers = []
students = [] students = []
@@ -44956,7 +44958,7 @@ def _getCourseAliasesMembers(croom, courseId, courseShowProperties, teachersFiel
printGettingEntityItemForWhom(Ent.TEACHER, formatKeyValueList('', [Ent.Singular(Ent.COURSE), courseId], currentCount(i, count))) printGettingEntityItemForWhom(Ent.TEACHER, formatKeyValueList('', [Ent.Singular(Ent.COURSE), courseId], currentCount(i, count)))
pageMessage = getPageMessage() pageMessage = getPageMessage()
try: try:
teachers = callGAPIpages(croom.courses().teachers(), 'list', 'teachers', teachers = callGAPIpages(ocroom.courses().teachers(), 'list', 'teachers',
pageMessage=pageMessage, pageMessage=pageMessage,
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.SERVICE_NOT_AVAILABLE], throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
@@ -44970,7 +44972,7 @@ def _getCourseAliasesMembers(croom, courseId, courseShowProperties, teachersFiel
printGettingEntityItemForWhom(Ent.STUDENT, formatKeyValueList('', [Ent.Singular(Ent.COURSE), courseId], currentCount(i, count))) printGettingEntityItemForWhom(Ent.STUDENT, formatKeyValueList('', [Ent.Singular(Ent.COURSE), courseId], currentCount(i, count)))
pageMessage = getPageMessage() pageMessage = getPageMessage()
try: try:
students = callGAPIpages(croom.courses().students(), 'list', 'students', students = callGAPIpages(ocroom.courses().students(), 'list', 'students',
pageMessage=pageMessage, pageMessage=pageMessage,
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.SERVICE_NOT_AVAILABLE], throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
@@ -44981,18 +44983,23 @@ def _getCourseAliasesMembers(croom, courseId, courseShowProperties, teachersFiel
ClientAPIAccessDeniedExit() ClientAPIAccessDeniedExit()
return (aliases, teachers, students) return (aliases, teachers, students)
def _doInfoCourses(entityList): def _doInfoCourses(courseIdList):
croom = buildGAPIObject(API.CLASSROOM) croom = buildGAPIObject(API.CLASSROOM)
courseShowProperties = _initCourseShowProperties() courseShowProperties = _initCourseShowProperties()
courseShowProperties['ownerEmail'] = True courseShowProperties['ownerEmail'] = True
ownerEmails = {} ownerEmails = {}
useOwnerAccess = False
FJQC = FormatJSONQuoteChar() FJQC = FormatJSONQuoteChar()
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
myarg = getArgument() myarg = getArgument()
if _getCourseShowProperties(myarg, courseShowProperties): if _getCourseShowProperties(myarg, courseShowProperties):
pass pass
elif myarg in OWNER_ACCESS_OPTIONS:
useOwnerAccess = True
else: else:
FJQC.GetFormatJSON(myarg) FJQC.GetFormatJSON(myarg)
coursesInfo = {}
_getCoursesOwnerInfo(croom, courseIdList, coursesInfo, not useOwnerAccess)
fields = _setCourseFields(courseShowProperties, False) fields = _setCourseFields(courseShowProperties, False)
if courseShowProperties['members'] != 'none': if courseShowProperties['members'] != 'none':
if courseShowProperties['countsOnly']: if courseShowProperties['countsOnly']:
@@ -45004,10 +45011,13 @@ def _doInfoCourses(entityList):
else: else:
teachersFields = studentsFields = None teachersFields = studentsFields = None
i = 0 i = 0
count = len(entityList) count = len(courseIdList)
for course in entityList: for courseId in courseIdList:
i += 1 i += 1
courseId = addCourseIdScope(course) courseId = addCourseIdScope(courseId)
courseInfo = coursesInfo[courseId]
if not courseInfo:
continue
try: try:
course = callGAPI(croom.courses(), 'get', course = callGAPI(croom.courses(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.SERVICE_NOT_AVAILABLE], throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.SERVICE_NOT_AVAILABLE],
@@ -45016,7 +45026,7 @@ def _doInfoCourses(entityList):
if courseShowProperties['ownerEmail']: if courseShowProperties['ownerEmail']:
course['ownerEmail'] = _convertCourseUserIdToEmail(croom, course['ownerId'], ownerEmails, course['ownerEmail'] = _convertCourseUserIdToEmail(croom, course['ownerId'], ownerEmails,
[Ent.COURSE, course['id'], Ent.OWNER_ID, course['ownerId']], i, count) [Ent.COURSE, course['id'], Ent.OWNER_ID, course['ownerId']], i, count)
aliases, teachers, students = _getCourseAliasesMembers(croom, courseId, courseShowProperties, teachersFields, studentsFields) aliases, teachers, students = _getCourseAliasesMembers(croom, courseInfo['croom'], courseId, courseShowProperties, teachersFields, studentsFields)
if FJQC.formatJSON: if FJQC.formatJSON:
if courseShowProperties['aliases']: if courseShowProperties['aliases']:
course.update({'aliases': list(aliases)}) course.update({'aliases': list(aliases)})
@@ -45308,7 +45318,7 @@ def doPrintCourses():
if showItemCountOnly: if showItemCountOnly:
itemCount += 1 itemCount += 1
continue continue
aliases, teachers, students = _getCourseAliasesMembers(croom, courseId, courseShowProperties, teachersFields, studentsFields, True, i, count) aliases, teachers, students = _getCourseAliasesMembers(croom, croom, courseId, courseShowProperties, teachersFields, studentsFields, True, i, count)
if courseShowProperties['aliases']: if courseShowProperties['aliases']:
if not courseShowProperties['aliasesInColumns']: if not courseShowProperties['aliasesInColumns']:
course['Aliases'] = delimiter.join([removeCourseAliasScope(alias['alias']) for alias in aliases]) course['Aliases'] = delimiter.join([removeCourseAliasScope(alias['alias']) for alias in aliases])
@@ -46051,7 +46061,7 @@ def doPrintCourseParticipants():
for course in coursesInfo: for course in coursesInfo:
i += 1 i += 1
courseId = course['id'] courseId = course['id']
_, teachers, students = _getCourseAliasesMembers(croom, courseId, courseShowProperties, teachersFields, studentsFields, True, i, count) _, teachers, students = _getCourseAliasesMembers(croom, croom, courseId, courseShowProperties, teachersFields, studentsFields, True, i, count)
if showItemCountOnly: if showItemCountOnly:
if courseShowProperties['members'] != 'students': if courseShowProperties['members'] != 'students':
itemCount += len(teachers) itemCount += len(teachers)
@@ -46362,31 +46372,37 @@ def doCourseAddItems(courseIdList, getEntityListArg):
# gam course <CourseID> remove alias <CourseAlias> # gam course <CourseID> remove alias <CourseAlias>
# gam courses <CourseEntity> remove topic <CourseTopicIDEntity> # gam courses <CourseEntity> remove topic <CourseTopicIDEntity>
# gam course <CourseID> remove topic <CourseTopicID> # gam course <CourseID> remove topic <CourseTopicID>
# gam courses <CourseEntity> remove teachers|students <UserTypeEntity> # gam courses <CourseEntity> remove teachers|students [owneracccess] <UserTypeEntity>
# gam course <CourseID> remove teacher|student <EmailAddress> # gam course <CourseID> remove teacher|student [owneracccess] <EmailAddress>
def doCourseRemoveItems(courseIdList, getEntityListArg): def doCourseRemoveItems(courseIdList, getEntityListArg):
croom = buildGAPIObject(API.CLASSROOM) croom = buildGAPIObject(API.CLASSROOM)
role = getChoice(ADD_REMOVE_PARTICIPANT_TYPES_MAP, mapChoice=True) role = getChoice(ADD_REMOVE_PARTICIPANT_TYPES_MAP, mapChoice=True)
coursesInfo = {} coursesInfo = {}
if not getEntityListArg: if not getEntityListArg:
if role in {Ent.STUDENT, Ent.TEACHER}: if role in {Ent.STUDENT, Ent.TEACHER}:
useOwnerAccess = checkArgumentPresent(OWNER_ACCESS_OPTIONS)
removeItems = getStringReturnInList(Cmd.OB_EMAIL_ADDRESS) removeItems = getStringReturnInList(Cmd.OB_EMAIL_ADDRESS)
elif role == Ent.COURSE_ALIAS: elif role == Ent.COURSE_ALIAS:
useOwnerAccess = False
removeItems = getStringReturnInList(Cmd.OB_COURSE_ALIAS) removeItems = getStringReturnInList(Cmd.OB_COURSE_ALIAS)
else: # role == Ent.COURSE_TOPIC: else: # role == Ent.COURSE_TOPIC:
useOwnerAccess = True
removeItems = getStringReturnInList(Cmd.OB_COURSE_TOPIC_ID) removeItems = getStringReturnInList(Cmd.OB_COURSE_TOPIC_ID)
courseParticipantLists = None courseParticipantLists = None
else: else:
if role in {Ent.STUDENT, Ent.TEACHER}: if role in {Ent.STUDENT, Ent.TEACHER}:
useOwnerAccess = checkArgumentPresent(OWNER_ACCESS_OPTIONS)
_, removeItems = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS, _, removeItems = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS,
typeMap={Cmd.ENTITY_COURSEPARTICIPANTS: PARTICIPANT_EN_MAP[role]}) typeMap={Cmd.ENTITY_COURSEPARTICIPANTS: PARTICIPANT_EN_MAP[role]})
elif role == Ent.COURSE_ALIAS: elif role == Ent.COURSE_ALIAS:
useOwnerAccess = False
removeItems = getEntityList(Cmd.OB_COURSE_ALIAS_ENTITY, shlexSplit=True) removeItems = getEntityList(Cmd.OB_COURSE_ALIAS_ENTITY, shlexSplit=True)
else: # role == Ent.COURSE_TOPIC: else: # role == Ent.COURSE_TOPIC:
useOwnerAccess = True
removeItems = getEntityList(Cmd.OB_COURSE_TOPIC_ID_ENTITY, shlexSplit=True) removeItems = getEntityList(Cmd.OB_COURSE_TOPIC_ID_ENTITY, shlexSplit=True)
courseParticipantLists = removeItems if isinstance(removeItems, dict) else None courseParticipantLists = removeItems if isinstance(removeItems, dict) else None
checkForExtraneousArguments() checkForExtraneousArguments()
_getCoursesOwnerInfo(croom, courseIdList, coursesInfo, role != Ent.COURSE_TOPIC) _getCoursesOwnerInfo(croom, courseIdList, coursesInfo, not useOwnerAccess)
i = 0 i = 0
count = len(courseIdList) count = len(courseIdList)
for courseId in courseIdList: for courseId in courseIdList:
@@ -52378,6 +52394,8 @@ def extendFileTreeParents(drive, fileTree, fields):
if not result.get('driveId'): if not result.get('driveId'):
result['parents'] = [ORPHANS] if result.get('ownedByMe', False) else [SHARED_WITHME] result['parents'] = [ORPHANS] if result.get('ownedByMe', False) else [SHARED_WITHME]
else: else:
if result['name'] == TEAM_DRIVE:
result['name'] = _getSharedDriveNameFromId(drive, result['driveId'])
result['parents'] = [SHARED_DRIVES] if 'sharedWithMeTime' not in f_file else [SHARED_WITHME] result['parents'] = [SHARED_DRIVES] if 'sharedWithMeTime' not in f_file else [SHARED_WITHME]
fileTree[fileId]['info'] = result fileTree[fileId]['info'] = result
fileTree[fileId]['info']['noDisplay'] = True fileTree[fileId]['info']['noDisplay'] = True
@@ -58133,12 +58151,13 @@ def collectOrphans(users):
try: try:
callGAPI(drive.files(), 'update', callGAPI(drive.files(), 'update',
bailOnInternalError=True, bailOnInternalError=True,
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.INTERNAL_ERROR, GAPI.INSUFFICIENT_PARENT_PERMISSIONS], throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.BAD_REQUEST, GAPI.FILE_NOT_FOUND,
GAPI.INTERNAL_ERROR, GAPI.INSUFFICIENT_PARENT_PERMISSIONS],
retryReasons=[GAPI.FILE_NOT_FOUND], retryReasons=[GAPI.FILE_NOT_FOUND],
fileId=fileId, body={}, addParents=newParentId, fields='') fileId=fileId, body={}, addParents=newParentId, fields='')
entityModifierNewValueItemValueListActionPerformed([Ent.USER, user, fileType, fileName], entityModifierNewValueItemValueListActionPerformed([Ent.USER, user, fileType, fileName],
Act.MODIFIER_INTO, None, [Ent.DRIVE_FOLDER, trgtUserFolderName], j, jcount) Act.MODIFIER_INTO, None, [Ent.DRIVE_FOLDER, trgtUserFolderName], j, jcount)
except (GAPI.fileNotFound, GAPI.internalError, GAPI.insufficientParentPermissions,) as e: except (GAPI.badRequest, GAPI.fileNotFound, GAPI.internalError, GAPI.insufficientParentPermissions,) as e:
entityActionFailedWarning([Ent.USER, user, fileType, fileName], str(e), j, jcount) entityActionFailedWarning([Ent.USER, user, fileType, fileName], str(e), j, jcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e: except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
userSvcNotApplicableOrDriveDisabled(user, str(e), i, count) userSvcNotApplicableOrDriveDisabled(user, str(e), i, count)
@@ -59184,7 +59203,7 @@ def transferOwnership(users):
try: try:
fileEntryInfo = callGAPI(drive.files(), 'get', fileEntryInfo = callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS, throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=fileId, fields='id,name,parents,mimeType,ownedByMe,trashed') fileId=fileId, fields='id,name,parents,mimeType,ownedByMe,trashed,shortcutDetails')
except GAPI.fileNotFound: except GAPI.fileNotFound:
entityActionFailedWarning(kvList, Msg.NOT_FOUND, j, jcount) entityActionFailedWarning(kvList, Msg.NOT_FOUND, j, jcount)
continue continue
@@ -59205,6 +59224,8 @@ def transferOwnership(users):
if changeParents: if changeParents:
filesToTransfer[fileId]['addParents'] = addParents filesToTransfer[fileId]['addParents'] = addParents
filesToTransfer[fileId]['removeParents'] = ','.join(fileEntryInfo.get('parents', [])) filesToTransfer[fileId]['removeParents'] = ','.join(fileEntryInfo.get('parents', []))
if fileEntryInfo['mimeType'] == MIMETYPE_GA_SHORTCUT and entityType != Ent.DRIVE_SHORTCUT:
filesToTransfer[fileId]['shortcutDetails'] = fileEntryInfo['shortcutDetails']
if fileEntryInfo['mimeType'] == MIMETYPE_GA_FOLDER and not noRecursion: if fileEntryInfo['mimeType'] == MIMETYPE_GA_FOLDER and not noRecursion:
if buildTree: if buildTree:
_identifyFilesToTransfer(fileEntry) _identifyFilesToTransfer(fileEntry)
@@ -59228,7 +59249,7 @@ def transferOwnership(users):
fileDesc = f'{fileInfo["name"]} ({xferFileId})' fileDesc = f'{fileInfo["name"]} ({xferFileId})'
kvList = [Ent.USER, user, entityType, fileDesc] kvList = [Ent.USER, user, entityType, fileDesc]
try: try:
if entityType != Ent.DRIVE_SHORTCUT: if entityType not in {Ent.DRIVE_SHORTCUT, Ent.DRIVE_FILE_SHORTCUT, Ent.DRIVE_FOLDER_SHORTCUT}:
if changeParents: if changeParents:
removeParents = fileInfo.get('removeParents', '') removeParents = fileInfo.get('removeParents', '')
if removeParents: if removeParents:
@@ -59244,7 +59265,16 @@ def transferOwnership(users):
fileId=xferFileId, permissionId=permissionId, transferOwnership=True, body=body, fields='') fileId=xferFileId, permissionId=permissionId, transferOwnership=True, body=body, fields='')
entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_TO, None, [Ent.USER, newOwner], k, kcount) entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_TO, None, [Ent.USER, newOwner], k, kcount)
else: else:
entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_TO, None, [Ent.USER, newOwner], k, kcount) if changeParents and entityType != Ent.DRIVE_SHORTCUT:
callGAPI(drive.files(), 'delete',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS,
fileId=xferFileId, supportsAllDrives=True)
action = Act.Get()
Act.Set(Act.DELETE_SHORTCUT)
entityActionPerformed(kvList, k, kcount)
Act.Set(action)
else:
entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_TO, None, [Ent.USER, newOwner], k, kcount)
except GAPI.permissionNotFound: except GAPI.permissionNotFound:
# this might happen if target user isn't explicitly in ACL (i.e. shared with anyone) # this might happen if target user isn't explicitly in ACL (i.e. shared with anyone)
try: try:
@@ -59279,11 +59309,11 @@ def transferOwnership(users):
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e: except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
userSvcNotApplicableOrDriveDisabled(user, str(e), i, count) userSvcNotApplicableOrDriveDisabled(user, str(e), i, count)
break break
if entityType != Ent.DRIVE_SHORTCUT: kvList = [Ent.USER, newOwner, entityType, fileDesc]
if changeParents and 'addParents' in fileInfo: try:
kvList = [Ent.USER, newOwner, entityType, fileDesc] if entityType not in {Ent.DRIVE_SHORTCUT, Ent.DRIVE_FILE_SHORTCUT, Ent.DRIVE_FOLDER_SHORTCUT}:
try: if changeParents and 'addParents' in fileInfo:
if entityType != Ent.DRIVE_SHORTCUT: if entityType != Ent.DRIVE_FILE_SHORTCUT:
callGAPI(targetDrive.files(), 'update', callGAPI(targetDrive.files(), 'update',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.CANNOT_ADD_PARENT, GAPI.INSUFFICIENT_PARENT_PERMISSIONS], throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.CANNOT_ADD_PARENT, GAPI.INSUFFICIENT_PARENT_PERMISSIONS],
fileId=xferFileId, addParents=fileInfo['addParents'], fields='', supportsAllDrives=True) fileId=xferFileId, addParents=fileInfo['addParents'], fields='', supportsAllDrives=True)
@@ -59291,13 +59321,27 @@ def transferOwnership(users):
Act.Set(Act.ADD) Act.Set(Act.ADD)
entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_TO, None, [Ent.DRIVE_FOLDER, fileInfo['addParents']], k, kcount) entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_TO, None, [Ent.DRIVE_FOLDER, fileInfo['addParents']], k, kcount)
Act.Set(action) Act.Set(action)
except GAPI.fileNotFound: else:
entityActionFailedWarning(kvList, Msg.DOES_NOT_EXIST, k, kcount) if changeParents and 'addParents' in fileInfo and entityType != Ent.DRIVE_SHORTCUT:
except (GAPI.forbidden, GAPI.cannotAddParent, GAPI.insufficientPermissions, GAPI.insufficientParentPermissions, body = {'name': fileInfo['name'], 'mimeType': MIMETYPE_GA_SHORTCUT,
GAPI.invalid, GAPI.badRequest, GAPI.unknownError) as e: 'parents': [fileInfo['addParents']], 'shortcutDetails': {'targetId': fileInfo['shortcutDetails']['targetId']}}
entityActionFailedWarning(kvList, str(e), k, kcount) callGAPI(targetDrive.files(), 'create',
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e: throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.FORBIDDEN, GAPI.INSUFFICIENT_PERMISSIONS, GAPI.INSUFFICIENT_PARENT_PERMISSIONS,
userSvcNotApplicableOrDriveDisabled(newOwner, str(e), 0, 0) GAPI.INVALID, GAPI.BAD_REQUEST, GAPI.FILE_NOT_FOUND, GAPI.UNKNOWN_ERROR,
GAPI.STORAGE_QUOTA_EXCEEDED, GAPI.TEAMDRIVES_SHARING_RESTRICTION_NOT_ALLOWED,
GAPI.TEAMDRIVE_FILE_LIMIT_EXCEEDED, GAPI.TEAMDRIVE_HIERARCHY_TOO_DEEP, GAPI.SHORTCUT_TARGET_INVALID,
GAPI.TARGET_USER_ROLE_LIMITED_BY_LICENSE_RESTRICTION],
body=body, fields='id', supportsAllDrives=True)
Act.Set(Act.CREATE_SHORTCUT)
entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_IN, None, [Ent.DRIVE_FOLDER, fileInfo['addParents']], k, kcount)
Act.Set(action)
except GAPI.fileNotFound:
entityActionFailedWarning(kvList, Msg.DOES_NOT_EXIST, k, kcount)
except (GAPI.forbidden, GAPI.cannotAddParent, GAPI.insufficientPermissions, GAPI.insufficientParentPermissions,
GAPI.invalid, GAPI.badRequest, GAPI.unknownError) as e:
entityActionFailedWarning(kvList, str(e), k, kcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
userSvcNotApplicableOrDriveDisabled(newOwner, str(e), 0, 0)
Ind.Decrement() Ind.Decrement()
Ind.Decrement() Ind.Decrement()
Ind.Decrement() Ind.Decrement()
@@ -59501,7 +59545,7 @@ def claimOwnership(users):
fileEntryInfo = callGAPI(drive.files(), 'get', fileEntryInfo = callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_GET_THROW_REASONS, throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
fileId=fileId, fileId=fileId,
fields='id,name,parents,mimeType,ownedByMe,trashed,owners(emailAddress,permissionId)') fields='id,name,parents,mimeType,ownedByMe,trashed,shortcutDetails,owners(emailAddress,permissionId)')
except GAPI.fileNotFound: except GAPI.fileNotFound:
entityActionFailedWarning(kvList, Msg.NOT_FOUND, j, jcount) entityActionFailedWarning(kvList, Msg.NOT_FOUND, j, jcount)
continue continue
@@ -59529,6 +59573,8 @@ def claimOwnership(users):
if changeParents: if changeParents:
filesToClaim[owner][fileId]['addParents'] = addParents filesToClaim[owner][fileId]['addParents'] = addParents
filesToClaim[owner][fileId]['removeParents'] = ','.join(fileEntryInfo.get('parents', [])) filesToClaim[owner][fileId]['removeParents'] = ','.join(fileEntryInfo.get('parents', []))
if fileEntryInfo['mimeType'] == MIMETYPE_GA_SHORTCUT and entityType != Ent.DRIVE_SHORTCUT:
filesToClaim[owner][fileId]['shortcutDetails'] = fileEntryInfo['shortcutDetails']
if fileEntryInfo['mimeType'] == MIMETYPE_GA_FOLDER: if fileEntryInfo['mimeType'] == MIMETYPE_GA_FOLDER:
if buildTree: if buildTree:
_identifyFilesToClaim(fileEntry) _identifyFilesToClaim(fileEntry)
@@ -59565,7 +59611,7 @@ def claimOwnership(users):
fileDesc = f'{fileInfo["name"]} ({xferFileId})' fileDesc = f'{fileInfo["name"]} ({xferFileId})'
kvList = [Ent.USER, oldOwner, entityType, fileDesc] kvList = [Ent.USER, oldOwner, entityType, fileDesc]
try: try:
if entityType != Ent.DRIVE_SHORTCUT: if entityType not in {Ent.DRIVE_SHORTCUT, Ent.DRIVE_FILE_SHORTCUT, Ent.DRIVE_FOLDER_SHORTCUT}:
if bodyShare: if bodyShare:
callGAPI(sourceDrive.files(), 'update', callGAPI(sourceDrive.files(), 'update',
fileId=xferFileId, body=bodyShare, fields='') fileId=xferFileId, body=bodyShare, fields='')
@@ -59587,8 +59633,17 @@ def claimOwnership(users):
entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_FROM, None, [Ent.USER, oldOwner], l, lcount) entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_FROM, None, [Ent.USER, oldOwner], l, lcount)
_processRetainedRole(user, i, count, oldOwner, entityType, xferFileId, fileDesc, l, lcount) _processRetainedRole(user, i, count, oldOwner, entityType, xferFileId, fileDesc, l, lcount)
else: else:
kvList = [Ent.USER, user, entityType, fileDesc] if changeParents and entityType != Ent.DRIVE_SHORTCUT:
entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_FROM, None, [Ent.USER, oldOwner], l, lcount) callGAPI(sourceDrive.files(), 'delete',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS,
fileId=xferFileId, supportsAllDrives=True)
action = Act.Get()
Act.Set(Act.DELETE_SHORTCUT)
entityActionPerformed(kvList, l, lcount)
Act.Set(action)
else:
kvList = [Ent.USER, user, entityType, fileDesc]
entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_FROM, None, [Ent.USER, oldOwner], l, lcount)
except GAPI.permissionNotFound: except GAPI.permissionNotFound:
# if claimer not in ACL (file might be visible for all with link) # if claimer not in ACL (file might be visible for all with link)
try: try:
@@ -59624,23 +59679,37 @@ def claimOwnership(users):
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e: except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
userSvcNotApplicableOrDriveDisabled(user, str(e), i, count) userSvcNotApplicableOrDriveDisabled(user, str(e), i, count)
break break
if entityType != Ent.DRIVE_SHORTCUT: kvList = [Ent.USER, user, entityType, fileDesc]
if changeParents and 'addParents' in fileInfo: try:
kvList = [Ent.USER, user, entityType, fileDesc] if entityType not in {Ent.DRIVE_SHORTCUT, Ent.DRIVE_FILE_SHORTCUT, Ent.DRIVE_FOLDER_SHORTCUT}:
try: if changeParents and 'addParents' in fileInfo:
callGAPI(drive.files(), 'update', callGAPI(drive.files(), 'update',
throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.CANNOT_ADD_PARENT, GAPI.INSUFFICIENT_PARENT_PERMISSIONS], throwReasons=GAPI.DRIVE_ACCESS_THROW_REASONS+[GAPI.CANNOT_ADD_PARENT, GAPI.INSUFFICIENT_PARENT_PERMISSIONS],
fileId=xferFileId, addParents=fileInfo['addParents'], fields='', supportsAllDrives=True) fileId=xferFileId, addParents=fileInfo['addParents'], fields='', supportsAllDrives=True)
action = Act.Get() action = Act.Get()
Act.Set(Act.ADD) Act.Set(Act.ADD)
entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_TO, None, [Ent.DRIVE_FOLDER, fileInfo['addParents']], l, lcount) entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_TO, None, [Ent.DRIVE_FOLDER, fileInfo['addParents']], l, lcount)
Act.Set(action)
else:
if changeParents and 'addParents' in fileInfo and entityType != Ent.DRIVE_SHORTCUT:
body = {'name': fileInfo['name'], 'mimeType': MIMETYPE_GA_SHORTCUT,
'parents': [fileInfo['addParents']], 'shortcutDetails': {'targetId': fileInfo['shortcutDetails']['targetId']}}
callGAPI(drive.files(), 'create',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.FORBIDDEN, GAPI.INSUFFICIENT_PERMISSIONS, GAPI.INSUFFICIENT_PARENT_PERMISSIONS,
GAPI.INVALID, GAPI.BAD_REQUEST, GAPI.FILE_NOT_FOUND, GAPI.UNKNOWN_ERROR,
GAPI.STORAGE_QUOTA_EXCEEDED, GAPI.TEAMDRIVES_SHARING_RESTRICTION_NOT_ALLOWED,
GAPI.TEAMDRIVE_FILE_LIMIT_EXCEEDED, GAPI.TEAMDRIVE_HIERARCHY_TOO_DEEP, GAPI.SHORTCUT_TARGET_INVALID,
GAPI.TARGET_USER_ROLE_LIMITED_BY_LICENSE_RESTRICTION],
body=body, fields='id', supportsAllDrives=True)
Act.Set(Act.CREATE_SHORTCUT)
entityModifierNewValueItemValueListActionPerformed(kvList, Act.MODIFIER_IN, None, [Ent.DRIVE_FOLDER, fileInfo['addParents']], l, lcount)
Act.Set(action) Act.Set(action)
except GAPI.fileNotFound: except GAPI.fileNotFound:
entityActionFailedWarning(kvList, Msg.DOES_NOT_EXIST, l, lcount) entityActionFailedWarning(kvList, Msg.DOES_NOT_EXIST, l, lcount)
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions, GAPI.cannotAddParent) as e: except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions, GAPI.cannotAddParent) as e:
entityActionFailedWarning(kvList, str(e), l, lcount) entityActionFailedWarning(kvList, str(e), l, lcount)
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e: except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
userSvcNotApplicableOrDriveDisabled(user, str(e), i, count) userSvcNotApplicableOrDriveDisabled(user, str(e), i, count)
Ind.Decrement() Ind.Decrement()
else: else:
entityPerformActionModifierNumItemsModifier([Ent.USER, user], 'Not Performed', kcount, Ent.DRIVE_FILE_OR_FOLDER, entityPerformActionModifierNumItemsModifier([Ent.USER, user], 'Not Performed', kcount, Ent.DRIVE_FILE_OR_FOLDER,
@@ -72883,7 +72952,7 @@ def ProcessGAMCommand(args, processGamCfg=True, inLoop=False, closeSTD=True):
CROS_COMMANDS_WITH_OBJECTS[CL_command][CMD_FUNCTION][CL_objectName](entityList) CROS_COMMANDS_WITH_OBJECTS[CL_command][CMD_FUNCTION][CL_objectName](entityList)
sys.exit(GM.Globals[GM.SYSEXITRC]) sys.exit(GM.Globals[GM.SYSEXITRC])
except KeyboardInterrupt: except KeyboardInterrupt:
batchWriteStderr('Control-C\n') batchWriteStderr('\nControl-C\n')
setSysExitRC(KEYBOARD_INTERRUPT_RC) setSysExitRC(KEYBOARD_INTERRUPT_RC)
showAPICallsRetryData() showAPICallsRetryData()
adjustRedirectedSTDFilesIfNotMultiprocessing() adjustRedirectedSTDFilesIfNotMultiprocessing()

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2023 Ross Scroggs All Rights Reserved. # Copyright (C) 2024 Ross Scroggs All Rights Reserved.
# #
# All Rights Reserved. # All Rights Reserved.
# #
@@ -539,6 +539,10 @@ _SVCACCT_SCOPES = [
'api': CLASSROOM, 'api': CLASSROOM,
'subscopes': [], 'subscopes': [],
'scope': 'https://www.googleapis.com/auth/classroom.profile.emails'}, 'scope': 'https://www.googleapis.com/auth/classroom.profile.emails'},
{'name': 'Classroom API - Profile Photos',
'api': CLASSROOM,
'subscopes': [],
'scope': 'https://www.googleapis.com/auth/classroom.profile.photos'},
{'name': 'Classroom API - Rosters', {'name': 'Classroom API - Rosters',
'api': CLASSROOM, 'api': CLASSROOM,
'subscopes': READONLY, 'subscopes': READONLY,

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (C) 2023 Ross Scroggs All Rights Reserved. # Copyright (C) 2024 Ross Scroggs All Rights Reserved.
# #
# All Rights Reserved. # All Rights Reserved.
# #
@@ -115,11 +115,13 @@ LIMIT_EXCEEDED = 'limitExceeded'
LOGIN_REQUIRED = 'loginRequired' LOGIN_REQUIRED = 'loginRequired'
MALFORMED_WORKING_LOCATION_EVENT = 'malformedWorkingLocationEvent' MALFORMED_WORKING_LOCATION_EVENT = 'malformedWorkingLocationEvent'
MEMBER_NOT_FOUND = 'memberNotFound' MEMBER_NOT_FOUND = 'memberNotFound'
MYDRIVE_HIERARCHY_DEPTH_LIMIT_EXCEEDED = 'myDriveHierarchyDepthLimitExceeded'
NO_LIST_TEAMDRIVES_ADMINISTRATOR_PRIVILEGE = 'noListTeamDrivesAdministratorPrivilege' NO_LIST_TEAMDRIVES_ADMINISTRATOR_PRIVILEGE = 'noListTeamDrivesAdministratorPrivilege'
NO_MANAGE_TEAMDRIVE_ADMINISTRATOR_PRIVILEGE = 'noManageTeamDriveAdministratorPrivilege' NO_MANAGE_TEAMDRIVE_ADMINISTRATOR_PRIVILEGE = 'noManageTeamDriveAdministratorPrivilege'
NOT_A_CALENDAR_USER = 'notACalendarUser' NOT_A_CALENDAR_USER = 'notACalendarUser'
NOT_FOUND = 'notFound' NOT_FOUND = 'notFound'
NOT_IMPLEMENTED = 'notImplemented' NOT_IMPLEMENTED = 'notImplemented'
NUM_CHILDREN_IN_NON_ROOT_LIMIT_EXCEEDED = 'numChildrenInNonRootLimitExceeded'
OPERATION_NOT_SUPPORTED = 'operationNotSupported' OPERATION_NOT_SUPPORTED = 'operationNotSupported'
ORGANIZER_ON_NON_TEAMDRIVE_NOT_SUPPORTED = 'organizerOnNonTeamDriveNotSupported' ORGANIZER_ON_NON_TEAMDRIVE_NOT_SUPPORTED = 'organizerOnNonTeamDriveNotSupported'
ORGANIZER_ON_NON_TEAMDRIVE_ITEM_NOT_SUPPORTED = 'organizerOnNonTeamDriveItemNotSupported' ORGANIZER_ON_NON_TEAMDRIVE_ITEM_NOT_SUPPORTED = 'organizerOnNonTeamDriveItemNotSupported'
@@ -181,7 +183,7 @@ SERVICE_NOT_AVAILABLE_RETRY_REASONS = [SERVICE_NOT_AVAILABLE]
ACTIVITY_THROW_REASONS = [SERVICE_NOT_AVAILABLE, BAD_REQUEST] ACTIVITY_THROW_REASONS = [SERVICE_NOT_AVAILABLE, BAD_REQUEST]
ALERT_THROW_REASONS = [SERVICE_NOT_AVAILABLE, AUTH_ERROR] ALERT_THROW_REASONS = [SERVICE_NOT_AVAILABLE, AUTH_ERROR]
CALENDAR_THROW_REASONS = [SERVICE_NOT_AVAILABLE, AUTH_ERROR, NOT_A_CALENDAR_USER] CALENDAR_THROW_REASONS = [SERVICE_NOT_AVAILABLE, AUTH_ERROR, NOT_A_CALENDAR_USER]
CIGROUP_CREATE_THROW_REASONS = [SERVICE_NOT_AVAILABLE, ALREADY_EXISTS, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, INVALID, INVALID_ARGUMENT, PERMISSION_DENIED] CIGROUP_CREATE_THROW_REASONS = [SERVICE_NOT_AVAILABLE, ALREADY_EXISTS, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, INVALID, INVALID_ARGUMENT, PERMISSION_DENIED, FAILED_PRECONDITION]
CIGROUP_GET_THROW_REASONS = [SERVICE_NOT_AVAILABLE, NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, BAD_REQUEST, INVALID, SYSTEM_ERROR, PERMISSION_DENIED] CIGROUP_GET_THROW_REASONS = [SERVICE_NOT_AVAILABLE, NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, BAD_REQUEST, INVALID, SYSTEM_ERROR, PERMISSION_DENIED]
CIGROUP_LIST_THROW_REASONS = [SERVICE_NOT_AVAILABLE, RESOURCE_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, BAD_REQUEST, INVALID, INVALID_ARGUMENT, SYSTEM_ERROR, PERMISSION_DENIED] CIGROUP_LIST_THROW_REASONS = [SERVICE_NOT_AVAILABLE, RESOURCE_NOT_FOUND, DOMAIN_NOT_FOUND, DOMAIN_CANNOT_USE_APIS, FORBIDDEN, BAD_REQUEST, INVALID, INVALID_ARGUMENT, SYSTEM_ERROR, PERMISSION_DENIED]
CIGROUP_LIST_USERKEY_THROW_REASONS = CIGROUP_LIST_THROW_REASONS+[INVALID_ARGUMENT] CIGROUP_LIST_USERKEY_THROW_REASONS = CIGROUP_LIST_THROW_REASONS+[INVALID_ARGUMENT]