Added use_classroom_owner_access Boolean variable to gam.cfg

This commit is contained in:
Ross Scroggs
2024-02-18 20:59:58 -08:00
parent d949ca2cad
commit d5255615fd
10 changed files with 363 additions and 211 deletions

View File

@@ -3,9 +3,10 @@
- [Notes](#notes) - [Notes](#notes)
- [Definitions](#definitions) - [Definitions](#definitions)
- [Create classroom invitations](#create-classroom-invitations) - [Create classroom invitations](#create-classroom-invitations)
- [Accept classroom invitations](#accept-classroom-invitations) - [Accept classroom invitations by user](#accept-classroom-invitations-by-user)
- [Delete classroom invitations](#delete-classroom-invitations) - [Delete classroom invitations by user](#delete-classroom-invitations-by-user)
- [Display classroom invitations by user](#display-classroom-invitations-by-user) - [Display classroom invitations by user](#display-classroom-invitations-by-user)
- [Delete classroom invitations by course](#delete-classroom-invitations-by-course)
- [Display classroom invitations by course](#display-classroom-invitations-by-course) - [Display classroom invitations by course](#display-classroom-invitations-by-course)
## API documentation ## API documentation
@@ -24,8 +25,6 @@ Scope: https://www.googleapis.com/auth/classroom.rosters , Checked: FA
``` ```
Follow the directions to authorize the Service Account scopes. Follow the directions to authorize the Service Account scopes.
The Classroom API does not support inviting users from outside your domain.
## Definitions ## Definitions
``` ```
<DomainName> ::= <String>(.<String>)+ <DomainName> ::= <String>(.<String>)+
@@ -49,12 +48,18 @@ The Classroom API does not support inviting users from outside your domain.
Invite users to classes. Invite users to classes.
``` ```
gam <UserTypeEntity> create classroominvitation courses <CourseEntity> [role owner|student|teacher] gam <UserTypeEntity> create classroominvitation courses <CourseEntity> [role owner|student|teacher]
[adminaccess|asadmin] [csvformat] [todrive <ToDriveAttributes>*] [formatjson [quotechar <Character>]] [adminaccess|asadmin]
[csv|csvformat] [todrive <ToDriveAttributes>*] [formatjson [quotechar <Character>]]
``` ```
If `role` is not specified, `student` will be used. If `role` is not specified, `student` will be used.
You can only invite a co-teacher to be an owner of a course.
By default, classroom invitations are issued by the owner of the course, the `adminaccess` option causes the invitations to be issued by the admin named in `oauth2.txt`. By default, classroom invitations are issued by the owner of the course, the `adminaccess` option causes the invitations to be issued by the admin named in `oauth2.txt`.
By default, when an invitation is created, GAM outputs details of the invitation as indented keywords and values.
* `csv|csvformat [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]]` - Output the details in CSV format.
### Example ### Example
Suppose you have a CSV file CourseStudent.csv with two columns: Course,Student. Suppose you have a CSV file CourseStudent.csv with two columns: Course,Student.
@@ -66,11 +71,13 @@ This command will invite all students to their courses in parallel
``` ```
gam redirect stdout ./Invites.out multiprocess redirect stderr stdout multiprocess csv CourseStudent.csv gam user ~Student create classroominvitation role student course ~Course gam redirect stdout ./Invites.out multiprocess redirect stderr stdout multiprocess csv CourseStudent.csv gam user ~Student create classroominvitation role student course ~Course
``` ```
## Accept classroom invitations ## Accept classroom invitations by user
Accept classroom invitations for users. You can only invite a co-teacher to be an owner of a course. Accept classroom invitations for users.
``` ```
gam <UserTypeEntity> accept classroominvitation (ids <ClassroomInvitationIDEntity>)|([courses <CourseEntity>] [role all|owner|student|teacher]) gam <UserTypeEntity> accept classroominvitation (ids <ClassroomInvitationIDEntity>)|([courses <CourseEntity>] [role all|owner|student|teacher])
``` ```
`<UserTypeEntity>` must specify users in your domain.
By default, all invitations for the specified users will be accepted. By default, all invitations for the specified users will be accepted.
Select specific invitations to accept: Select specific invitations to accept:
@@ -81,11 +88,13 @@ Select courses and accept invitations for those courses.
By default, invitations for all roles will be accepted; you can limit the acceptances to invitations of a specific role. By default, invitations for all roles will be accepted; you can limit the acceptances to invitations of a specific role.
## Delete classroom invitations ## Delete classroom invitations by user
Delete classroom invitations for users. Delete classroom invitations for users.
``` ```
gam <UserTypeEntity> delete classroominvitation (ids <ClassroomInvitationIDEntity>)|([courses <CourseEntity>] [role all|owner|student|teacher]) gam <UserTypeEntity> delete classroominvitation (ids <ClassroomInvitationIDEntity>)|([courses <CourseEntity>] [role all|owner|student|teacher])
``` ```
`<UserTypeEntity>` must specify users in your domain.
By default, all invitations for the specified users will be deleted. By default, all invitations for the specified users will be deleted.
Select specific invitations to delete: Select specific invitations to delete:
@@ -104,8 +113,23 @@ gam <UserTypeEntity> show classroominvitations [role all|owner|student|teacher]
gam <UserTypeEntity> print classroominvitations [todrive <ToDriveAttributes>*] [role all|owner|student|teacher] gam <UserTypeEntity> print classroominvitations [todrive <ToDriveAttributes>*] [role all|owner|student|teacher]
[formatjson [quotechar <Character>]] [formatjson [quotechar <Character>]]
``` ```
`<UserTypeEntity>` must specify users in your domain.
By default, invitations for all roles will be displayed; you can limit the display to invitations of a specific role. By default, invitations for all roles will be displayed; you can limit the display to invitations of a specific role.
## Delete classroom invitations by course
Delete classroom invitations for courses. This command must be used to delete non-domain member invitations.
```
gam delete classroominvitation courses <CourseEntity> (ids <ClassroomInvitationIDEntity>)|(role all|owner|student|teacher)
```
Select courses and delete invitations for those courses.
* `courses <CourseEntity>` - Specify courses
Select specific invitations to delete:
* `ids <ClassroomInvitationIDEntity>` - Specify invitation IDs
Select invitations to delete by role. By default, invitations for all roles will be deleted; you can limit the deletions to invitations of a specific role.
## Display classroom invitations by course ## Display classroom invitations by course
``` ```
gam show classroominvitations (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>]) gam show classroominvitations (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])

View File

@@ -48,10 +48,7 @@ The `quotechar <Character>` option allows you to choose an alternate quote chara
## Display File Ownership for Old files ## Display File Ownership for Old files
If the above commands fail, you can try to loop through all accounts, however this might take a long time if you are on a large Google Workspace Account. If the above commands fail, you can try to loop through all accounts, however this might take a long time if you are on a large Google Workspace Account.
```
gam config auto_batch_min 1 redirect csv - multiprocess redirect stderr null multiprocess all users print filelist select id <DriveFileID> fields id,name,owners.emailaddress norecursion showownedby any
```
Starting with version 6.07.26, this can be made more efficient by terminating processing after the owner is identified.
``` ```
gam config auto_batch_min 1 multiprocessexit rc=0 redirect csv - multiprocess redirect stderr null multiprocess all users print filelist select id <DriveFileID> fields id,name,owners.emailaddress norecursion showownedby any gam config auto_batch_min 1 multiprocessexit rc=0 redirect csv - multiprocess redirect stderr null multiprocess all users print filelist select id <DriveFileID> fields id,name,owners.emailaddress norecursion showownedby any
gam config auto_batch_min 1 multiprocessexit rc=0 redirect csv - multiprocess redirect stderr null multiprocess all users print filelist select name <DriveFileName> fields id,name,owners.emailaddress norecursion showownedby any
``` ```

View File

@@ -10,6 +10,26 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
See [Downloads](https://github.com/taers232c/GAMADV-XTD3/wiki/Downloads) for Windows or other options, including manual installation See [Downloads](https://github.com/taers232c/GAMADV-XTD3/wiki/Downloads) for Windows or other options, including manual installation
### 6.69.00
Added `use_classroom_owner_access` Boolean variable to `gam.cfg` that controls how GAM gets
classroom member information and removes students/teachers. Client access does not provide
complete information about non-domain students/teachers.
* `False` - Use client access; this is the default. Use if you don't have non-domain members in your courses.
* `True` - Use service account access as the classroom owner. An extra API call is required per course to authenticate the owner; this will affect performance
Added the following command which must be used to delete classroom invitations for non-domain students/teachers.
```
gam delete classroominvitation courses <CourseEntity> (ids <ClassroomInvitationIDEntity>)|(role all|owner|student|teacher)
```
You can obtain the classroom invitation IDs with these commands:
```
gam show classroominvitations (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
[role all|owner|student|teacher] [formatjson]
gam print classroominvitations [todrive <ToDriveAttribute>*] (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
[role all|owner|student|teacher] [formatjson [quotechar <Character>]]
```
### 6.68.08 ### 6.68.08
Updated `gam <UserTypeEntity> print filelist|drivefileacls|shareddriveacls ... oneitemperrow` to print Updated `gam <UserTypeEntity> print filelist|drivefileacls|shareddriveacls ... oneitemperrow` to print

View File

@@ -334,7 +334,7 @@ writes the credentials into the file oauth2.txt.
admin@server:/Users/admin/bin/gamadv-xtd3$ rm -f /Users/admin/GAMConfig/oauth2.txt admin@server:/Users/admin/bin/gamadv-xtd3$ rm -f /Users/admin/GAMConfig/oauth2.txt
admin@server:/Users/admin/bin/gamadv-xtd3$ ./gam version admin@server:/Users/admin/bin/gamadv-xtd3$ ./gam version
WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found
GAMADV-XTD3 6.68.08 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource GAMADV-XTD3 6.69.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com> Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.2 64-bit final Python 3.12.2 64-bit final
MacOS Sonoma 14.2.1 x86_64 MacOS Sonoma 14.2.1 x86_64
@@ -1002,7 +1002,7 @@ writes the credentials into the file oauth2.txt.
C:\GAMADV-XTD3>del C:\GAMConfig\oauth2.txt C:\GAMADV-XTD3>del C:\GAMConfig\oauth2.txt
C:\GAMADV-XTD3>gam version C:\GAMADV-XTD3>gam version
WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found
GAMADV-XTD3 6.68.08 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource GAMADV-XTD3 6.69.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com> Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.2 64-bit final Python 3.12.2 64-bit final
Windows-10-10.0.17134 AMD64 Windows-10-10.0.17134 AMD64

View File

@@ -13,3 +13,4 @@ Thank you.
* Goldy Arora - https://www.goldyarora.com/license-notifier/ * Goldy Arora - https://www.goldyarora.com/license-notifier/
* Paul Ogier (Taming.Tech) - GAMADV-XTD3 Tutorials https://www.youtube.com/watch?v=g9LDeyXQNLI&list=PL_dLiK09pJVhKJxZHNk9CHK0q5hkZ856w * Paul Ogier (Taming.Tech) - GAMADV-XTD3 Tutorials https://www.youtube.com/watch?v=g9LDeyXQNLI&list=PL_dLiK09pJVhKJxZHNk9CHK0q5hkZ856w
* Paul Ogier (Taming.Tech) - GAMADV-XTD3 Course on Udemy https://taming.tech/GAMCourse * Paul Ogier (Taming.Tech) - GAMADV-XTD3 Course on Udemy https://taming.tech/GAMCourse
* Paul Ogier (Taming.Tech) - https://taming.tech/taming-gam-a-practical-guide-to-gam-and-gamadv-xtd3/

View File

@@ -3,7 +3,7 @@
Print the current version of Gam with details Print the current version of Gam with details
``` ```
gam version gam version
GAMADV-XTD3 6.68.08 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource GAMADV-XTD3 6.69.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com> Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.2 64-bit final Python 3.12.2 64-bit final
MacOS Sonoma 14.2.1 x86_64 MacOS Sonoma 14.2.1 x86_64
@@ -15,7 +15,7 @@ Time: 2023-06-02T21:10:00-07:00
Print the current version of Gam with details and time offset information Print the current version of Gam with details and time offset information
``` ```
gam version timeoffset gam version timeoffset
GAMADV-XTD3 6.68.08 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource GAMADV-XTD3 6.69.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com> Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.2 64-bit final Python 3.12.2 64-bit final
MacOS Sonoma 14.2.1 x86_64 MacOS Sonoma 14.2.1 x86_64
@@ -27,7 +27,7 @@ Your system time differs from www.googleapis.com by less than 1 second
Print the current version of Gam with extended details and SSL information Print the current version of Gam with extended details and SSL information
``` ```
gam version extended gam version extended
GAMADV-XTD3 6.68.08 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource GAMADV-XTD3 6.69.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com> Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.2 64-bit final Python 3.12.2 64-bit final
MacOS Sonoma 14.2.1 x86_64 MacOS Sonoma 14.2.1 x86_64
@@ -64,7 +64,7 @@ MacOS High Sierra 10.13.6 x86_64
Path: /Users/Admin/bin/gamadv-xtd3 Path: /Users/Admin/bin/gamadv-xtd3
Version Check: Version Check:
Current: 5.35.08 Current: 5.35.08
Latest: 6.68.08 Latest: 6.69.00
echo $? echo $?
1 1
``` ```
@@ -72,7 +72,7 @@ echo $?
Print the current version number without details Print the current version number without details
``` ```
gam version simple gam version simple
6.68.08 6.69.00
``` ```
In Linux/MacOS you can do: In Linux/MacOS you can do:
``` ```
@@ -82,7 +82,7 @@ echo $VER
Print the current version of Gam and address of this Wiki Print the current version of Gam and address of this Wiki
``` ```
gam help gam help
GAM 6.68.08 - https://github.com/taers232c/GAMADV-XTD3 GAM 6.69.00 - https://github.com/taers232c/GAMADV-XTD3
Ross Scroggs <ross.scroggs@gmail.com> Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.2 64-bit final Python 3.12.2 64-bit final
MacOS Sonoma 14.2.1 x86_64 MacOS Sonoma 14.2.1 x86_64

View File

@@ -573,6 +573,13 @@ update_cros_ou_with_id
Set to true if you are getting the following error: Set to true if you are getting the following error:
`400: invalidInput - Invalid Input: Inconsistent Orgunit id and path in request` `400: invalidInput - Invalid Input: Inconsistent Orgunit id and path in request`
Default: False Default: False
use_classroom_owner_access
How is classroom member information obtained and how are classroom members deleted.
Client access does not provide complete information about non-domain students/teachers.
When False, GAM uses client access to get classroom member information and to delete members
When True, GAM uses service account access as the classroom owner.
An extra API call is required per course to authenticate the owner
Default: False
use_projectid_as_name use_projectid_as_name
When False, new projects have a default project name of "GAM Project" When False, new projects have a default project name of "GAM Project"
and a default app name of "GAM". and a default app name of "GAM".

View File

@@ -2876,7 +2876,7 @@ gam course <CourseID> delete topic <CourseTopicID>
gam course <CourseID> create|add teachers [makefirstteacherowner] <UserItem> gam course <CourseID> create|add teachers [makefirstteacherowner] <UserItem>
gam course <CourseID> create|add students <UserItem> gam course <CourseID> create|add students <UserItem>
gam course <CourseID> delete|remove teachers|students <UserItem> gam course <CourseID> delete|remove teachers|students [owneraccess] <UserItem>
gam course <CourseID> clear teachers|students gam course <CourseID> clear teachers|students
gam course <CourseID> sync teachers [addonly|removeonly] [makefirstteacherowner] <UserTypeEntity> gam course <CourseID> sync teachers [addonly|removeonly] [makefirstteacherowner] <UserTypeEntity>
gam course <CourseID> sync students [addonly|removeonly] <UserTypeEntity> gam course <CourseID> sync students [addonly|removeonly] <UserTypeEntity>
@@ -2889,15 +2889,17 @@ gam courses <CourseEntity> delete topic <CourseTopicIDEntity>
gam courses <CourseEntity> create|add teachers [makefirstteacherowner] <UserTypeEntity> gam courses <CourseEntity> create|add teachers [makefirstteacherowner] <UserTypeEntity>
gam courses <CourseEntity> create|add students <UserTypeEntity> gam courses <CourseEntity> create|add students <UserTypeEntity>
gam courses <CourseEntity> delete|remove teachers|students <UserTypeEntity> gam courses <CourseEntity> delete|remove teachers|students [owneraccess] <UserTypeEntity>
gam courses <CourseEntity> clear teachers|students gam courses <CourseEntity> clear teachers|students
gam courses <CourseEntity> sync teachers [addonly|removeonly] [makefirstteacherowner] <UserTypeEntity> gam courses <CourseEntity> sync teachers [addonly|removeonly] [makefirstteacherowner] <UserTypeEntity>
gam courses <CourseEntity> sync students [addonly|removeonly] <UserTypeEntity> gam courses <CourseEntity> sync students [addonly|removeonly] <UserTypeEntity>
gam info course <CourseID> [owneremail] [alias|aliases] [show all|students|teachers] [countsonly] gam info course <CourseID> [owneraccess]
[owneremail] [alias|aliases] [show all|students|teachers] [countsonly]
[fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>]
[formatjson] [formatjson]
gam info courses <CourseEntity> [owneremail] [alias|aliases] [show all|students|teachers] [countsonly] gam info courses <CourseEntity> [owneraccess]
[owneremail] [alias|aliases] [show all|students|teachers] [countsonly]
[fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>]
[formatjson] [formatjson]
gam print courses [todrive <ToDriveAttribute>*] gam print courses [todrive <ToDriveAttribute>*]
@@ -3041,8 +3043,8 @@ gam print course-works [todrive <ToDriveAttribute>*]
# Classroom - Invitations # Classroom - Invitations
gam <UserTypeEntity> create classroominvitation courses <CourseEntity> [role owner|student|teacher] gam <UserTypeEntity> create classroominvitation courses <CourseEntity> [role owner|student|teacher]
[adminaccess|asadmin] [csv|csvformat] [todrive <ToDriveAttribute>*] [adminaccess|asadmin]
[formatjson [quotechar <Character>]] [csv|csvformat] [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]]
gam <UserTypeEntity> accept classroominvitation (ids <ClassroomInvitationIDEntity>)|([courses <CourseEntity>] [role all|owner|student|teacher]) gam <UserTypeEntity> accept classroominvitation (ids <ClassroomInvitationIDEntity>)|([courses <CourseEntity>] [role all|owner|student|teacher])
gam <UserTypeEntity> delete classroominvitation (ids <ClassroomInvitationIDEntity>)|([courses <CourseEntity>] [role all|owner|student|teacher]) gam <UserTypeEntity> delete classroominvitation (ids <ClassroomInvitationIDEntity>)|([courses <CourseEntity>] [role all|owner|student|teacher])
gam <UserTypeEntity> show classroominvitations [role all|owner|student|teacher] gam <UserTypeEntity> show classroominvitations [role all|owner|student|teacher]
@@ -3050,6 +3052,7 @@ gam <UserTypeEntity> show classroominvitations [role all|owner|student|teacher]
gam <UserTypeEntity> print classroominvitations [todrive <ToDriveAttribute>*] [role all|owner|student|teacher] gam <UserTypeEntity> print classroominvitations [todrive <ToDriveAttribute>*] [role all|owner|student|teacher]
[formatjson [quotechar <Character>]] [formatjson [quotechar <Character>]]
gam delete classroominvitation courses <CourseEntity> (ids <ClassroomInvitationIDEntity>)|(role all|owner|student|teacher)
gam show classroominvitations (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>]) gam show classroominvitations (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
[role all|owner|student|teacher] [role all|owner|student|teacher]
[formatjson] [formatjson]

View File

@@ -2,6 +2,26 @@
Merged GAM-Team version Merged GAM-Team version
6.69.00
Added `use_classroom_owner_access` Boolean variable to `gam.cfg` that controls how GAM gets
classroom member information and removes students/teachers. Client access does not provide
complete information about non-domain students/teachers.
* `False` - Use client access; this is the default. Use if you don't have non-domain members in your courses.
* `True` - Use service account access as the classroom owner. An extra API call is required per course to authenticate the owner; this will affect performance
Added the following command which must be used to delete classroom invitations for non-domain students/teachers.
```
gam delete classroominvitation courses <CourseEntity> (ids <ClassroomInvitationIDEntity>)|(role all|owner|student|teacher)
```
You can obtain the classroom invitation IDs with these commands:
```
gam show classroominvitations (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
[role all|owner|student|teacher] [formatjson]
gam print classroominvitations [todrive <ToDriveAttribute>*] (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
[role all|owner|student|teacher] [formatjson [quotechar <Character>]]
```
6.68.08 6.68.08
Updated `gam <UserTypeEntity> print filelist|drivefileacls|shareddriveacls ... oneitemperrow` to print Updated `gam <UserTypeEntity> print filelist|drivefileacls|shareddriveacls ... oneitemperrow` to print

View File

@@ -3395,7 +3395,7 @@ def SetGlobalVariables():
def _getCfgHeaderFilter(sectionName, itemName): def _getCfgHeaderFilter(sectionName, itemName):
value = GM.Globals[GM.PARSER].get(sectionName, itemName) value = GM.Globals[GM.PARSER].get(sectionName, itemName)
headerFilters = [] headerFilters = []
if not value: if not value or (len(value) == 2 and _stringInQuotes(value)):
return headerFilters return headerFilters
splitStatus, filters = shlexSplitListStatus(value) splitStatus, filters = shlexSplitListStatus(value)
if splitStatus: if splitStatus:
@@ -3411,7 +3411,7 @@ def SetGlobalVariables():
def _getCfgHeaderForce(sectionName, itemName): def _getCfgHeaderForce(sectionName, itemName):
value = GM.Globals[GM.PARSER].get(sectionName, itemName) value = GM.Globals[GM.PARSER].get(sectionName, itemName)
headerForce = [] headerForce = []
if not value: if not value or (len(value) == 2 and _stringInQuotes(value)):
return headerForce return headerForce
splitStatus, headerForce = shlexSplitListStatus(value) splitStatus, headerForce = shlexSplitListStatus(value)
if not splitStatus: if not splitStatus:
@@ -6338,15 +6338,15 @@ def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isA
elif entityType in {Cmd.ENTITY_COURSEPARTICIPANTS, Cmd.ENTITY_TEACHERS, Cmd.ENTITY_STUDENTS}: elif entityType in {Cmd.ENTITY_COURSEPARTICIPANTS, Cmd.ENTITY_TEACHERS, Cmd.ENTITY_STUDENTS}:
croom = buildGAPIObject(API.CLASSROOM) croom = buildGAPIObject(API.CLASSROOM)
if not noListConversion: if not noListConversion:
courses = convertEntityToList(entity) courseIdList = convertEntityToList(entity)
else: else:
courses = [entity] courseIdList = [entity]
for course in courses: _, _, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, GC.Values[GC.USE_COURSE_OWNER_ACCESS])
courseId = addCourseIdScope(course) for courseId, courseInfo in coursesInfo.items():
try: try:
if entityType in {Cmd.ENTITY_COURSEPARTICIPANTS, Cmd.ENTITY_TEACHERS}: if entityType in {Cmd.ENTITY_COURSEPARTICIPANTS, Cmd.ENTITY_TEACHERS}:
printGettingAllEntityItemsForWhom(Ent.TEACHER, removeCourseIdScope(courseId), entityType=Ent.COURSE) printGettingAllEntityItemsForWhom(Ent.TEACHER, removeCourseIdScope(courseId), entityType=Ent.COURSE)
result = callGAPIpages(croom.courses().teachers(), 'list', 'teachers', result = callGAPIpages(courseInfo['croom'].courses().teachers(), 'list', 'teachers',
pageMessage=getPageMessageForWhom(), pageMessage=getPageMessageForWhom(),
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.BAD_REQUEST, GAPI.SERVICE_NOT_AVAILABLE], throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.BAD_REQUEST, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
@@ -6359,7 +6359,7 @@ def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isA
entityList.append(email) entityList.append(email)
if entityType in {Cmd.ENTITY_COURSEPARTICIPANTS, Cmd.ENTITY_STUDENTS}: if entityType in {Cmd.ENTITY_COURSEPARTICIPANTS, Cmd.ENTITY_STUDENTS}:
printGettingAllEntityItemsForWhom(Ent.STUDENT, removeCourseIdScope(courseId), entityType=Ent.COURSE) printGettingAllEntityItemsForWhom(Ent.STUDENT, removeCourseIdScope(courseId), entityType=Ent.COURSE)
result = callGAPIpages(croom.courses().students(), 'list', 'students', result = callGAPIpages(courseInfo['croom'].courses().students(), 'list', 'students',
pageMessage=getPageMessageForWhom(), pageMessage=getPageMessageForWhom(),
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.BAD_REQUEST, GAPI.SERVICE_NOT_AVAILABLE], throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.BAD_REQUEST, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
@@ -17979,7 +17979,7 @@ def doPrintAliases():
except (GAPI.invalidOrgunit, GAPI.invalidInput): except (GAPI.invalidOrgunit, GAPI.invalidInput):
entityActionFailedWarning([Ent.ALIAS, None], invalidQuery(query)) entityActionFailedWarning([Ent.ALIAS, None], invalidQuery(query))
continue continue
except GAPI.domainNotFound as e : except GAPI.domainNotFound as e:
entityActionFailedWarning([Ent.ALIAS, None, Ent.DOMAIN, kwargs['domain']], str(e)) entityActionFailedWarning([Ent.ALIAS, None, Ent.DOMAIN, kwargs['domain']], str(e))
continue continue
except (GAPI.resourceNotFound, GAPI.forbidden, GAPI.badRequest): except (GAPI.resourceNotFound, GAPI.forbidden, GAPI.badRequest):
@@ -18008,13 +18008,16 @@ def doPrintAliases():
try: try:
entityList = callGAPIpages(cd.groups(), 'list', 'groups', entityList = callGAPIpages(cd.groups(), 'list', 'groups',
pageMessage=getPageMessage(showFirstLastItems=True), messageAttribute='email', pageMessage=getPageMessage(showFirstLastItems=True), messageAttribute='email',
throwReasons=GAPI.GROUP_LIST_THROW_REASONS, throwReasons=GAPI.GROUP_LIST_USERKEY_THROW_REASONS,
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
query=query, orderBy='email', query=query, orderBy='email',
fields=f'nextPageToken,groups({",".join(groupFields)})', **kwargs) fields=f'nextPageToken,groups({",".join(groupFields)})', **kwargs)
for group in entityList: for group in entityList:
writeAliases(group, group['email'], 'Group') writeAliases(group, group['email'], 'Group')
except GAPI.domainNotFound as e : except (GAPI.invalidMember, GAPI.invalidInput) as e:
if not invalidMember(query):
entityActionFailedExit([Ent.GROUP, None], str(e))
except GAPI.domainNotFound as e:
entityActionFailedWarning([Ent.ALIAS, None, Ent.DOMAIN, kwargs['domain']], str(e)) entityActionFailedWarning([Ent.ALIAS, None, Ent.DOMAIN, kwargs['domain']], str(e))
continue continue
except (GAPI.resourceNotFound, GAPI.forbidden, GAPI.badRequest): except (GAPI.resourceNotFound, GAPI.forbidden, GAPI.badRequest):
@@ -41517,7 +41520,7 @@ def doCreateUser():
throwReasons=[GAPI.DUPLICATE, GAPI.DOMAIN_NOT_FOUND, throwReasons=[GAPI.DUPLICATE, GAPI.DOMAIN_NOT_FOUND,
GAPI.DOMAIN_CANNOT_USE_APIS, GAPI.FORBIDDEN, GAPI.DOMAIN_CANNOT_USE_APIS, GAPI.FORBIDDEN,
GAPI.INVALID, GAPI.INVALID_INPUT, GAPI.INVALID_PARAMETER, GAPI.INVALID, GAPI.INVALID_INPUT, GAPI.INVALID_PARAMETER,
GAPI.INVALID_ORGUNIT, GAPI.INVALID_SCHEMA_VALUE], GAPI.INVALID_ORGUNIT, GAPI.INVALID_SCHEMA_VALUE, GAPI.CONDITION_NOT_MET],
body=body, body=body,
fields=fields, fields=fields,
resolveConflictAccount=resolveConflictAccount) resolveConflictAccount=resolveConflictAccount)
@@ -41536,7 +41539,7 @@ def doCreateUser():
except GAPI.invalidOrgunit: except GAPI.invalidOrgunit:
entityActionFailedExit([Ent.USER, user], Msg.INVALID_ORGUNIT) entityActionFailedExit([Ent.USER, user], Msg.INVALID_ORGUNIT)
except (GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden, except (GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
GAPI.invalid, GAPI.invalidInput, GAPI.invalidParameter) as e: GAPI.invalid, GAPI.invalidInput, GAPI.invalidParameter, GAPI.conditionNotMet) as e:
entityActionFailedExit([Ent.USER, user], str(e)) entityActionFailedExit([Ent.USER, user], str(e))
if PwdOpts.filename and PwdOpts.password: if PwdOpts.filename and PwdOpts.password:
writeFile(PwdOpts.filename, f'{user},{PwdOpts.password}\n', mode='a', continueOnError=True) writeFile(PwdOpts.filename, f'{user},{PwdOpts.password}\n', mode='a', continueOnError=True)
@@ -41698,7 +41701,7 @@ def updateUsers(entityList):
result = callGAPI(cd.users(), 'insert', result = callGAPI(cd.users(), 'insert',
throwReasons=[GAPI.DUPLICATE, GAPI.DOMAIN_NOT_FOUND, GAPI.FORBIDDEN, throwReasons=[GAPI.DUPLICATE, GAPI.DOMAIN_NOT_FOUND, GAPI.FORBIDDEN,
GAPI.INVALID, GAPI.INVALID_INPUT, GAPI.INVALID_PARAMETER, GAPI.INVALID, GAPI.INVALID_INPUT, GAPI.INVALID_PARAMETER,
GAPI.INVALID_ORGUNIT, GAPI.INVALID_SCHEMA_VALUE], GAPI.INVALID_ORGUNIT, GAPI.INVALID_SCHEMA_VALUE, GAPI.CONDITION_NOT_MET],
body=body, body=body,
fields=fields, fields=fields,
resolveConflictAccount=resolveConflictAccount) resolveConflictAccount=resolveConflictAccount)
@@ -41730,7 +41733,7 @@ def updateUsers(entityList):
entityActionFailedWarning([Ent.USER, user], Msg.INVALID_ORGUNIT, i, count) entityActionFailedWarning([Ent.USER, user], Msg.INVALID_ORGUNIT, i, count)
except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden, GAPI.badRequest, except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden, GAPI.badRequest,
GAPI.invalid, GAPI.invalidInput, GAPI.invalidParameter, GAPI.insufficientArchivedUserLicenses, GAPI.invalid, GAPI.invalidInput, GAPI.invalidParameter, GAPI.insufficientArchivedUserLicenses,
GAPI.conflict, GAPI.badRequest, GAPI.backendError, GAPI.systemError) as e: GAPI.conflict, GAPI.badRequest, GAPI.backendError, GAPI.systemError, GAPI.conditionNotMet) as e:
entityActionFailedWarning([Ent.USER, user], str(e), i, count) entityActionFailedWarning([Ent.USER, user], str(e), i, count)
# gam update users <UserTypeEntity> ... # gam update users <UserTypeEntity> ...
@@ -44212,6 +44215,8 @@ class CourseAttributes():
def __init__(self, croom, updateMode): def __init__(self, croom, updateMode):
self.croom = croom self.croom = croom
self.ocroom = croom
self.tcroom = None
self.updateMode = updateMode self.updateMode = updateMode
self.body = {} self.body = {}
self.courseId = None self.courseId = None
@@ -44371,15 +44376,20 @@ class CourseAttributes():
missingArgumentExit('copyfrom <CourseID>') missingArgumentExit('copyfrom <CourseID>')
else: else:
return True return True
# ocroom - copyfrom course owner
if self.announcementStates or self.materialStates or self.workStates or self.copyTopics or self.members != 'none':
_, self.ocroom = buildGAPIServiceObject(API.CLASSROOM, f'uid:{self.ownerId}')
if self.ocroom is None:
return False
if self.members != 'none': if self.members != 'none':
_, self.teachers, self.students = _getCourseAliasesMembers(self.croom, self.croom, self.courseId, {'members': self.members}, _, self.teachers, self.students = _getCourseAliasesMembers(self.croom, self.ocroom, 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:
printGettingAllEntityItemsForWhom(Ent.COURSE_ANNOUNCEMENT_ID, Ent.TypeName(Ent.COURSE, self.courseId), 0, 0, printGettingAllEntityItemsForWhom(Ent.COURSE_ANNOUNCEMENT_ID, Ent.TypeName(Ent.COURSE, self.courseId), 0, 0,
_gettingCourseEntityQuery(Ent.COURSE_ANNOUNCEMENT_STATE, self.announcementStates)) _gettingCourseEntityQuery(Ent.COURSE_ANNOUNCEMENT_STATE, self.announcementStates))
try: try:
self.courseAnnouncements = callGAPIpages(self.croom.courses().announcements(), 'list', 'announcements', self.courseAnnouncements = callGAPIpages(self.ocroom.courses().announcements(), 'list', 'announcements',
pageMessage=getPageMessageForWhom(), pageMessage=getPageMessageForWhom(),
throwReasons=GAPI.COURSE_ACCESS_THROW_REASONS+[GAPI.SERVICE_NOT_AVAILABLE], throwReasons=GAPI.COURSE_ACCESS_THROW_REASONS+[GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
@@ -44396,7 +44406,7 @@ class CourseAttributes():
printGettingAllEntityItemsForWhom(Ent.COURSE_MATERIAL_ID, Ent.TypeName(Ent.COURSE, self.courseId), 0, 0, printGettingAllEntityItemsForWhom(Ent.COURSE_MATERIAL_ID, Ent.TypeName(Ent.COURSE, self.courseId), 0, 0,
_gettingCourseEntityQuery(Ent.COURSE_MATERIAL_STATE, self.materialStates)) _gettingCourseEntityQuery(Ent.COURSE_MATERIAL_STATE, self.materialStates))
try: try:
self.courseMaterials = callGAPIpages(self.croom.courses().courseWorkMaterials(), 'list', 'courseWorkMaterial', self.courseMaterials = callGAPIpages(self.ocroom.courses().courseWorkMaterials(), 'list', 'courseWorkMaterial',
pageMessage=getPageMessageForWhom(), pageMessage=getPageMessageForWhom(),
throwReasons=GAPI.COURSE_ACCESS_THROW_REASONS+[GAPI.SERVICE_NOT_AVAILABLE], throwReasons=GAPI.COURSE_ACCESS_THROW_REASONS+[GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
@@ -44417,7 +44427,7 @@ class CourseAttributes():
printGettingAllEntityItemsForWhom(Ent.COURSE_WORK_ID, Ent.TypeName(Ent.COURSE, self.courseId), 0, 0, printGettingAllEntityItemsForWhom(Ent.COURSE_WORK_ID, Ent.TypeName(Ent.COURSE, self.courseId), 0, 0,
_gettingCourseEntityQuery(Ent.COURSE_WORK_STATE, self.workStates)) _gettingCourseEntityQuery(Ent.COURSE_WORK_STATE, self.workStates))
try: try:
self.courseWorks = callGAPIpages(self.croom.courses().courseWork(), 'list', 'courseWork', self.courseWorks = callGAPIpages(self.ocroom.courses().courseWork(), 'list', 'courseWork',
pageMessage=getPageMessageForWhom(), pageMessage=getPageMessageForWhom(),
throwReasons=GAPI.COURSE_ACCESS_THROW_REASONS+[GAPI.SERVICE_NOT_AVAILABLE], throwReasons=GAPI.COURSE_ACCESS_THROW_REASONS+[GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
@@ -44441,7 +44451,7 @@ class CourseAttributes():
if self.copyTopics: if self.copyTopics:
printGettingAllEntityItemsForWhom(Ent.COURSE_TOPIC, Ent.TypeName(Ent.COURSE, self.courseId), 0, 0) printGettingAllEntityItemsForWhom(Ent.COURSE_TOPIC, Ent.TypeName(Ent.COURSE, self.courseId), 0, 0)
try: try:
courseTopics = callGAPIpages(self.croom.courses().topics(), 'list', 'topic', courseTopics = callGAPIpages(self.ocroom.courses().topics(), 'list', 'topic',
pageMessage=getPageMessageForWhom(), pageMessage=getPageMessageForWhom(),
throwReasons=GAPI.COURSE_ACCESS_THROW_REASONS+[GAPI.SERVICE_NOT_AVAILABLE], throwReasons=GAPI.COURSE_ACCESS_THROW_REASONS+[GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
@@ -44517,14 +44527,16 @@ class CourseAttributes():
newCourseId = newCourse['id'] newCourseId = newCourse['id']
ownerId = newCourse['ownerId'] ownerId = newCourse['ownerId']
teacherFolderId = newCourse['teacherFolder']['id'] teacherFolderId = newCourse['teacherFolder']['id']
# tcroom - new/update course owner
if self.announcementStates or self.materialStates or self.workStates or self.copyTopics: if self.announcementStates or self.materialStates or self.workStates or self.copyTopics:
_, tcroom = buildGAPIServiceObject(API.CLASSROOM, f'uid:{ownerId}') _, self.tcroom = buildGAPIServiceObject(API.CLASSROOM, f'uid:{ownerId}')
if tcroom is None: if self.tcroom is None:
return return
if (self.announcementStates or self.materialStates or self.workStates) and self.copyMaterialsFiles: if (self.announcementStates or self.materialStates or self.workStates) and self.copyMaterialsFiles:
_, tdrive = buildGAPIServiceObject(API.DRIVE3, f'uid:{ownerId}') _, tdrive = buildGAPIServiceObject(API.DRIVE3, f'uid:{ownerId}')
if tdrive is None: if tdrive is None:
return return
# Adds are done with domain admin
if self.members in {'all', 'students'}: if self.members in {'all', 'students'}:
addParticipants = [student['profile']['emailAddress'] for student in self.students if 'emailAddress' in student['profile']] addParticipants = [student['profile']['emailAddress'] for student in self.students if 'emailAddress' in student['profile']]
_batchAddItemsToCourse(self.croom, newCourseId, i, count, addParticipants, Ent.STUDENT) _batchAddItemsToCourse(self.croom, newCourseId, i, count, addParticipants, Ent.STUDENT)
@@ -44533,7 +44545,7 @@ class CourseAttributes():
_batchAddItemsToCourse(self.croom, newCourseId, i, count, addParticipants, Ent.TEACHER) _batchAddItemsToCourse(self.croom, newCourseId, i, count, addParticipants, Ent.TEACHER)
if self.copyTopics: if self.copyTopics:
try: try:
newCourseTopics = callGAPIpages(self.croom.courses().topics(), 'list', 'topic', newCourseTopics = callGAPIpages(self.tcroom.courses().topics(), 'list', 'topic',
throwReasons=GAPI.COURSE_ACCESS_THROW_REASONS+[GAPI.FAILED_PRECONDITION, GAPI.SERVICE_NOT_AVAILABLE], throwReasons=GAPI.COURSE_ACCESS_THROW_REASONS+[GAPI.FAILED_PRECONDITION, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
courseId=newCourseId, fields='nextPageToken,topic(topicId,name)', courseId=newCourseId, fields='nextPageToken,topic(topicId,name)',
@@ -44556,7 +44568,7 @@ class CourseAttributes():
[Ent.COURSE, self.courseId], Msg.DUPLICATE, j, jcount) [Ent.COURSE, self.courseId], Msg.DUPLICATE, j, jcount)
continue continue
try: try:
result = callGAPI(tcroom.courses().topics(), 'create', result = callGAPI(self.tcroom.courses().topics(), 'create',
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.FAILED_PRECONDITION, GAPI.INVALID_ARGUMENT, GAPI.SERVICE_NOT_AVAILABLE], throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.FAILED_PRECONDITION, GAPI.INVALID_ARGUMENT, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
courseId=newCourseId, body={'name': topicName}, fields='topicId') courseId=newCourseId, body={'name': topicName}, fields='topicId')
@@ -44583,7 +44595,7 @@ class CourseAttributes():
if self.copyMaterialsFiles: if self.copyMaterialsFiles:
self.CopyMaterials(tdrive, newCourseId, body, Ent.COURSE_ANNOUNCEMENT_ID, courseAnnouncementId, teacherFolderId) self.CopyMaterials(tdrive, newCourseId, body, Ent.COURSE_ANNOUNCEMENT_ID, courseAnnouncementId, teacherFolderId)
try: try:
result = callGAPI(tcroom.courses().announcements(), 'create', result = callGAPI(self.tcroom.courses().announcements(), 'create',
throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.FORBIDDEN, throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.FORBIDDEN,
GAPI.BAD_REQUEST, GAPI.FAILED_PRECONDITION, GAPI.BACKEND_ERROR, GAPI.INTERNAL_ERROR, GAPI.SERVICE_NOT_AVAILABLE], GAPI.BAD_REQUEST, GAPI.FAILED_PRECONDITION, GAPI.BACKEND_ERROR, GAPI.INTERNAL_ERROR, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
@@ -44619,7 +44631,7 @@ class CourseAttributes():
if newTopicId: if newTopicId:
body['topicId'] = newTopicId body['topicId'] = newTopicId
try: try:
result = callGAPI(tcroom.courses().courseWorkMaterials(), 'create', result = callGAPI(self.tcroom.courses().courseWorkMaterials(), 'create',
throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.FORBIDDEN, throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.FORBIDDEN,
GAPI.BAD_REQUEST, GAPI.FAILED_PRECONDITION, GAPI.BACKEND_ERROR, GAPI.INTERNAL_ERROR, GAPI.SERVICE_NOT_AVAILABLE], GAPI.BAD_REQUEST, GAPI.FAILED_PRECONDITION, GAPI.BACKEND_ERROR, GAPI.INTERNAL_ERROR, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
@@ -44658,7 +44670,7 @@ class CourseAttributes():
body.pop('dueDate', None) body.pop('dueDate', None)
body.pop('dueTime', None) body.pop('dueTime', None)
try: try:
result = callGAPI(tcroom.courses().courseWork(), 'create', result = callGAPI(self.tcroom.courses().courseWork(), 'create',
bailOnInternalError=True, bailOnInternalError=True,
throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.FORBIDDEN, throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.FORBIDDEN,
GAPI.BAD_REQUEST, GAPI.FAILED_PRECONDITION, GAPI.BACKEND_ERROR, GAPI.BAD_REQUEST, GAPI.FAILED_PRECONDITION, GAPI.BACKEND_ERROR,
@@ -45003,6 +45015,30 @@ def _convertCourseUserIdToEmail(croom, userId, emails, entityValueList, i, count
emails[userId] = userEmail emails[userId] = userEmail
return userEmail return userEmail
def _getCoursesOwnerInfo(croom, courseIds, useOwnerAccess):
coursesInfo = {}
for courseId in courseIds:
courseId = addCourseIdScope(courseId)
if courseId not in coursesInfo:
try:
course = callGAPI(croom.courses(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
id=courseId, fields='name,ownerId')
if useOwnerAccess:
_, ocroom = buildGAPIServiceObject(API.CLASSROOM, f'uid:{course["ownerId"]}')
else:
ocroom = croom
if ocroom is not None:
coursesInfo[courseId] = {'name': course['name'], 'croom': ocroom}
except GAPI.notFound:
entityDoesNotExistWarning(Ent.COURSE, courseId)
except (GAPI.permissionDenied, GAPI.serviceNotAvailable) as e:
entityActionFailedWarning([Ent.COURSE, courseId], str(e))
except GAPI.forbidden:
ClientAPIAccessDeniedExit()
return 0, len(coursesInfo), coursesInfo
def _getCourseAliasesMembers(croom, ocroom, 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 = []
@@ -45059,7 +45095,7 @@ def _doInfoCourses(courseIdList):
courseShowProperties = _initCourseShowProperties() courseShowProperties = _initCourseShowProperties()
courseShowProperties['ownerEmail'] = True courseShowProperties['ownerEmail'] = True
ownerEmails = {} ownerEmails = {}
useOwnerAccess = False useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS]
FJQC = FormatJSONQuoteChar() FJQC = FormatJSONQuoteChar()
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
myarg = getArgument() myarg = getArgument()
@@ -45069,8 +45105,6 @@ def _doInfoCourses(courseIdList):
useOwnerAccess = True 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']:
@@ -45081,14 +45115,9 @@ def _doInfoCourses(courseIdList):
studentsFields = 'nextPageToken,students(profile)' studentsFields = 'nextPageToken,students(profile)'
else: else:
teachersFields = studentsFields = None teachersFields = studentsFields = None
i = 0 i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, useOwnerAccess)
count = len(courseIdList) for courseId, courseInfo in coursesInfo.items():
for courseId in courseIdList:
i += 1 i += 1
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],
@@ -45159,12 +45188,14 @@ def _doInfoCourses(courseIdList):
except GAPI.forbidden: except GAPI.forbidden:
ClientAPIAccessDeniedExit() ClientAPIAccessDeniedExit()
# gam info courses <CourseEntity> [owneremail] [alias|aliases] [show none|all|students|teachers] [countsonly] # gam info courses <CourseEntity> [owneraccess]
# [owneremail] [alias|aliases] [show none|all|students|teachers] [countsonly]
# [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] [formatjson] # [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] [formatjson]
def doInfoCourses(): def doInfoCourses():
_doInfoCourses(getEntityList(Cmd.OB_COURSE_ENTITY, shlexSplit=True)) _doInfoCourses(getEntityList(Cmd.OB_COURSE_ENTITY, shlexSplit=True))
# gam info course <CourseID> [owneremail] [alias|aliases] [show none|all|students|teachers] [countsonly] # gam info course <CourseID> [owneraccess]
# [owneremail] [alias|aliases] [show none|all|students|teachers] [countsonly]
# [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] [formatjson] # [fields <CourseFieldNameList>] [skipfields <CourseFieldNameList>] [formatjson]
def doInfoCourse(): def doInfoCourse():
_doInfoCourses(getStringReturnInList(Cmd.OB_COURSE_ID)) _doInfoCourses(getStringReturnInList(Cmd.OB_COURSE_ID))
@@ -45322,6 +45353,7 @@ def doPrintCourses():
ownerEmails = {} ownerEmails = {}
delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER] delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER]
showItemCountOnly = False showItemCountOnly = False
useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS]
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
myarg = getArgument() myarg = getArgument()
if myarg == 'todrive': if myarg == 'todrive':
@@ -45342,7 +45374,7 @@ def doPrintCourses():
if applyCourseItemFilter: if applyCourseItemFilter:
if courseShowProperties['fields']: if courseShowProperties['fields']:
courseShowProperties['fields'].append(courseItemFilter['timefilter']) courseShowProperties['fields'].append(courseItemFilter['timefilter'])
coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties) coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties, useOwnerAccess)
if coursesInfo is None: if coursesInfo is None:
if showItemCountOnly: if showItemCountOnly:
writeStdout('0\n') writeStdout('0\n')
@@ -45381,6 +45413,12 @@ def doPrintCourses():
for field in courseShowProperties['skips']: for field in courseShowProperties['skips']:
course.pop(field, None) course.pop(field, None)
courseId = course['id'] courseId = course['id']
if useOwnerAccess:
_, ocroom = buildGAPIServiceObject(API.CLASSROOM, f'uid:{course["ownerId"]}')
if not ocroom:
continue
else:
ocroom = croom
if courseShowProperties['ownerEmail']: if courseShowProperties['ownerEmail']:
course['ownerEmail'] = _convertCourseUserIdToEmail(croom, course['ownerId'], ownerEmails, course['ownerEmail'] = _convertCourseUserIdToEmail(croom, course['ownerId'], ownerEmails,
[Ent.COURSE, courseId, Ent.OWNER_ID, course['ownerId']], i, count) [Ent.COURSE, courseId, Ent.OWNER_ID, course['ownerId']], i, count)
@@ -45389,7 +45427,7 @@ def doPrintCourses():
if showItemCountOnly: if showItemCountOnly:
itemCount += 1 itemCount += 1
continue continue
aliases, teachers, students = _getCourseAliasesMembers(croom, croom, courseId, courseShowProperties, teachersFields, studentsFields, True, i, count) aliases, teachers, students = _getCourseAliasesMembers(croom, ocroom, 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])
@@ -45614,7 +45652,6 @@ def doPrintCourseTopics():
courseId = course['id'] courseId = course['id']
if courseTopicIdsLists: if courseTopicIdsLists:
courseTopicIds = courseTopicIdsLists[courseId] courseTopicIds = courseTopicIdsLists[courseId]
if not courseTopicIds: if not courseTopicIds:
fields = getItemFieldsFromFieldsList('topic', fieldsList) fields = getItemFieldsFromFieldsList('topic', fieldsList)
printGettingAllEntityItemsForWhom(Ent.COURSE_TOPIC, Ent.TypeName(Ent.COURSE, courseId), i, count) printGettingAllEntityItemsForWhom(Ent.COURSE_TOPIC, Ent.TypeName(Ent.COURSE, courseId), i, count)
@@ -46098,6 +46135,7 @@ def doPrintCourseParticipants():
courseShowProperties = _initCourseShowProperties(['name']) courseShowProperties = _initCourseShowProperties(['name'])
courseShowProperties['members'] = 'all' courseShowProperties['members'] = 'all'
showItemCountOnly = False showItemCountOnly = False
useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS]
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
myarg = getArgument() myarg = getArgument()
if myarg == 'todrive': if myarg == 'todrive':
@@ -46110,7 +46148,7 @@ def doPrintCourseParticipants():
showItemCountOnly = True showItemCountOnly = True
else: else:
FJQC.GetFormatJSONQuoteChar(myarg, False) FJQC.GetFormatJSONQuoteChar(myarg, False)
coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties) coursesInfo = _getCoursesInfo(croom, courseSelectionParameters, courseShowProperties, useOwnerAccess)
if coursesInfo is None: if coursesInfo is None:
if showItemCountOnly: if showItemCountOnly:
writeStdout('0\n') writeStdout('0\n')
@@ -46132,7 +46170,13 @@ def doPrintCourseParticipants():
for course in coursesInfo: for course in coursesInfo:
i += 1 i += 1
courseId = course['id'] courseId = course['id']
_, teachers, students = _getCourseAliasesMembers(croom, croom, courseId, courseShowProperties, teachersFields, studentsFields, True, i, count) if useOwnerAccess:
_, ocroom = buildGAPIServiceObject(API.CLASSROOM, f'uid:{course["ownerId"]}')
if not ocroom:
continue
else:
ocroom = croom
_, teachers, students = _getCourseAliasesMembers(croom, ocroom, 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)
@@ -46319,29 +46363,6 @@ def _batchRemoveItemsFromCourse(croom, courseId, i, count, removeParticipants, r
dbatch.execute() dbatch.execute()
Ind.Decrement() Ind.Decrement()
def _getCoursesOwnerInfo(croom, courseIds, coursesInfo, useAdminAccess):
for courseId in courseIds:
courseId = addCourseIdScope(courseId)
if courseId not in coursesInfo:
coursesInfo[courseId] = {}
try:
info = callGAPI(croom.courses(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
id=courseId, fields='name,ownerId')
if not useAdminAccess:
_, ocroom = buildGAPIServiceObject(API.CLASSROOM, f'uid:{info["ownerId"]}')
else:
ocroom = croom
if ocroom is not None:
coursesInfo[courseId] = {'name': info['name'], 'croom': ocroom}
except GAPI.notFound:
entityDoesNotExistWarning(Ent.COURSE, courseId)
except (GAPI.permissionDenied, GAPI.serviceNotAvailable) as e:
entityActionFailedWarning([Ent.COURSE, courseId], str(e))
except GAPI.forbidden:
ClientAPIAccessDeniedExit()
def _updateCourseOwner(croom, courseId, owner, i, count): def _updateCourseOwner(croom, courseId, owner, i, count):
action = Act.Get() action = Act.Get()
Act.Set(Act.UPDATE_OWNER) Act.Set(Act.UPDATE_OWNER)
@@ -46398,7 +46419,6 @@ def doCourseAddItems(courseIdList, getEntityListArg):
makeFirstTeacherOwner = checkArgumentPresent(['makefirstteacherowner']) makeFirstTeacherOwner = checkArgumentPresent(['makefirstteacherowner'])
else: else:
makeFirstTeacherOwner = False makeFirstTeacherOwner = False
coursesInfo = {}
if not getEntityListArg: if not getEntityListArg:
if role in {Ent.STUDENT, Ent.TEACHER}: if role in {Ent.STUDENT, Ent.TEACHER}:
addItems = getStringReturnInList(Cmd.OB_EMAIL_ADDRESS) addItems = getStringReturnInList(Cmd.OB_EMAIL_ADDRESS)
@@ -46422,19 +46442,14 @@ def doCourseAddItems(courseIdList, getEntityListArg):
if makeFirstTeacherOwner and addItems: if makeFirstTeacherOwner and addItems:
firstTeacher = normalizeEmailAddressOrUID(addItems[0]) firstTeacher = normalizeEmailAddressOrUID(addItems[0])
checkForExtraneousArguments() checkForExtraneousArguments()
_getCoursesOwnerInfo(croom, courseIdList, coursesInfo, role != Ent.COURSE_TOPIC) i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, role == Ent.COURSE_TOPIC)
i = 0 for courseId, courseInfo in coursesInfo.items():
count = len(courseIdList)
for courseId in courseIdList:
i += 1 i += 1
if courseParticipantLists: if courseParticipantLists:
addItems = courseParticipantLists[courseId] addItems = courseParticipantLists[courseId]
firstTeacher = None firstTeacher = None
if makeFirstTeacherOwner and addItems: if makeFirstTeacherOwner and addItems:
firstTeacher = normalizeEmailAddressOrUID(addItems[0]) firstTeacher = normalizeEmailAddressOrUID(addItems[0])
courseId = addCourseIdScope(courseId)
courseInfo = coursesInfo[courseId]
if courseInfo:
_batchAddItemsToCourse(courseInfo['croom'], courseId, i, count, addItems, role) _batchAddItemsToCourse(courseInfo['croom'], courseId, i, count, addItems, role)
if makeFirstTeacherOwner and firstTeacher: if makeFirstTeacherOwner and firstTeacher:
_updateCourseOwner(courseInfo['croom'], courseId, firstTeacher, i, count) _updateCourseOwner(courseInfo['croom'], courseId, firstTeacher, i, count)
@@ -46448,10 +46463,11 @@ def doCourseAddItems(courseIdList, getEntityListArg):
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 = {}
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) useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS]
if checkArgumentPresent(OWNER_ACCESS_OPTIONS):
useOwnerAccess = True
removeItems = getStringReturnInList(Cmd.OB_EMAIL_ADDRESS) removeItems = getStringReturnInList(Cmd.OB_EMAIL_ADDRESS)
elif role == Ent.COURSE_ALIAS: elif role == Ent.COURSE_ALIAS:
useOwnerAccess = False useOwnerAccess = False
@@ -46473,16 +46489,11 @@ def doCourseRemoveItems(courseIdList, getEntityListArg):
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, not useOwnerAccess) i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, useOwnerAccess)
i = 0 for courseId, courseInfo in coursesInfo.items():
count = len(courseIdList)
for courseId in courseIdList:
i += 1 i += 1
if courseParticipantLists: if courseParticipantLists:
removeItems = courseParticipantLists[courseId] removeItems = courseParticipantLists[courseId]
courseId = addCourseIdScope(courseId)
courseInfo = coursesInfo[courseId]
if courseInfo:
_batchRemoveItemsFromCourse(courseInfo['croom'], courseId, i, count, removeItems, role) _batchRemoveItemsFromCourse(courseInfo['croom'], courseId, i, count, removeItems, role)
# gam courses <CourseEntity> clear teachers|students # gam courses <CourseEntity> clear teachers|students
@@ -46491,14 +46502,13 @@ def doCourseClearParticipants(courseIdList, getEntityListArg):
croom = buildGAPIObject(API.CLASSROOM) croom = buildGAPIObject(API.CLASSROOM)
role = getChoice(CLEAR_SYNC_PARTICIPANT_TYPES_MAP, mapChoice=True) role = getChoice(CLEAR_SYNC_PARTICIPANT_TYPES_MAP, mapChoice=True)
checkForExtraneousArguments() checkForExtraneousArguments()
i = 0 i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, GC.Values[GC.USE_COURSE_OWNER_ACCESS])
count = len(courseIdList) for courseId, courseInfo in coursesInfo.items():
for courseId in courseIdList:
i += 1 i += 1
removeParticipants = getItemsToModify(PARTICIPANT_EN_MAP[role], courseId, noListConversion=True) removeParticipants = getItemsToModify(PARTICIPANT_EN_MAP[role], courseId, noListConversion=True)
if GM.Globals[GM.CLASSROOM_SERVICE_NOT_AVAILABLE]: if GM.Globals[GM.CLASSROOM_SERVICE_NOT_AVAILABLE]:
continue continue
_batchRemoveItemsFromCourse(croom, courseId, i, count, removeParticipants, role) _batchRemoveItemsFromCourse(courseInfo['croom'], courseId, i, count, removeParticipants, role)
# gam courses <CourseEntity> sync students [addonly|removeonly] <UserTypeEntity> # gam courses <CourseEntity> sync students [addonly|removeonly] <UserTypeEntity>
# gam course <CourseID> sync students [addonly|removeonly] <UserTypeEntity> # gam course <CourseID> sync students [addonly|removeonly] <UserTypeEntity>
@@ -46525,9 +46535,8 @@ def doCourseSyncParticipants(courseIdList, getEntityListArg):
syncParticipantsSet.add(normalizeEmailAddressOrUID(user)) syncParticipantsSet.add(normalizeEmailAddressOrUID(user))
if makeFirstTeacherOwner: if makeFirstTeacherOwner:
firstTeacher = normalizeEmailAddressOrUID(syncParticipants[0]) firstTeacher = normalizeEmailAddressOrUID(syncParticipants[0])
i = 0 i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, GC.Values[GC.USE_COURSE_OWNER_ACCESS])
count = len(courseIdList) for courseId, courseInfo in coursesInfo.items():
for courseId in courseIdList:
i += 1 i += 1
if courseParticipantLists: if courseParticipantLists:
syncParticipantsSet = set() syncParticipantsSet = set()
@@ -46537,9 +46546,6 @@ def doCourseSyncParticipants(courseIdList, getEntityListArg):
syncParticipantsSet.add(normalizeEmailAddressOrUID(user)) syncParticipantsSet.add(normalizeEmailAddressOrUID(user))
if makeFirstTeacherOwner: if makeFirstTeacherOwner:
firstTeacher = normalizeEmailAddressOrUID(courseParticipantLists[courseId][0]) firstTeacher = normalizeEmailAddressOrUID(courseParticipantLists[courseId][0])
courseInfo = checkCourseExists(croom, courseId, i, count)
if courseInfo:
courseId = courseInfo['id']
currentParticipantsSet = set() currentParticipantsSet = set()
currentParticipants = getItemsToModify(PARTICIPANT_EN_MAP[role], courseId, noListConversion=True) currentParticipants = getItemsToModify(PARTICIPANT_EN_MAP[role], courseId, noListConversion=True)
if GM.Globals[GM.CLASSROOM_SERVICE_NOT_AVAILABLE]: if GM.Globals[GM.CLASSROOM_SERVICE_NOT_AVAILABLE]:
@@ -46551,7 +46557,7 @@ def doCourseSyncParticipants(courseIdList, getEntityListArg):
if makeFirstTeacherOwner and firstTeacher: if makeFirstTeacherOwner and firstTeacher:
_updateCourseOwner(croom, courseId, firstTeacher, i, count) _updateCourseOwner(croom, courseId, firstTeacher, i, count)
if syncOperation != 'addonly': if syncOperation != 'addonly':
_batchRemoveItemsFromCourse(croom, courseId, i, count, list(currentParticipantsSet-syncParticipantsSet), role) _batchRemoveItemsFromCourse(courseInfo['croom'], courseId, i, count, list(currentParticipantsSet-syncParticipantsSet), role)
def studentUnknownWarning(studentId, errMsg, i, count): def studentUnknownWarning(studentId, errMsg, i, count):
setSysExitRC(SERVICE_NOT_APPLICABLE_RC) setSysExitRC(SERVICE_NOT_APPLICABLE_RC)
@@ -47208,9 +47214,8 @@ def createClassroomInvitations(users):
croom = buildGAPIObject(API.CLASSROOM) croom = buildGAPIObject(API.CLASSROOM)
classroomEmails = {} classroomEmails = {}
courseIds = None courseIds = None
coursesInfo = {}
role = CLASSROOM_ROLE_STUDENT role = CLASSROOM_ROLE_STUDENT
useAdminAccess = False useOwnerAccess = True
csvPF = None csvPF = None
FJQC = FormatJSONQuoteChar(csvPF) FJQC = FormatJSONQuoteChar(csvPF)
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
@@ -47225,20 +47230,20 @@ def createClassroomInvitations(users):
elif csvPF and myarg == 'todrive': elif csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters() csvPF.GetTodriveParameters()
elif myarg in ADMIN_ACCESS_OPTIONS: elif myarg in ADMIN_ACCESS_OPTIONS:
useAdminAccess = True useOwnerAccess = False
else: else:
FJQC.GetFormatJSONQuoteChar(myarg, False) FJQC.GetFormatJSONQuoteChar(myarg, False)
if courseIds is None: if courseIds is None:
missingArgumentExit('courses <CourseEntity>') missingArgumentExit('courses <CourseEntity>')
if csvPF: if csvPF:
if FJQC.formatJSON: if FJQC.formatJSON:
csvPF.SetTitles(['userEmail', 'JSON']) csvPF.SetJSONTitles(['userEmail', 'JSON'])
else: else:
csvPF.SetTitles(['userId', 'userEmail', 'courseId', 'courseName', 'id', 'role']) csvPF.SetTitles(['userEmail', 'courseId', 'courseName', 'id', 'role'])
csvPF.SetSortAllTitles() csvPF.SetSortAllTitles()
courseIdsLists = courseIds if isinstance(courseIds, dict) else None courseIdsLists = courseIds if isinstance(courseIds, dict) else None
if courseIdsLists is None: if courseIdsLists is None:
_getCoursesOwnerInfo(croom, courseIds, coursesInfo, useAdminAccess) j, jcount, coursesInfo = _getCoursesOwnerInfo(croom, courseIds, useOwnerAccess)
entityType = CLASSROOM_ROLE_ENTITY_MAP[role] entityType = CLASSROOM_ROLE_ENTITY_MAP[role]
i, count, users = getEntityArgument(users) i, count, users = getEntityArgument(users)
for user in users: for user in users:
@@ -47246,25 +47251,20 @@ def createClassroomInvitations(users):
userId = normalizeEmailAddressOrUID(user) userId = normalizeEmailAddressOrUID(user)
userEmail = _getClassroomEmail(croom, classroomEmails, userId, userId) userEmail = _getClassroomEmail(croom, classroomEmails, userId, userId)
if courseIdsLists: if courseIdsLists:
courseIds = courseIdsLists[user] j, jcount, coursesInfo = _getCoursesOwnerInfo(croom, courseIdsLists[user], useOwnerAccess)
_getCoursesOwnerInfo(croom, courseIds, coursesInfo, useAdminAccess)
jcount = len(courseIds)
if csvPF or not FJQC.formatJSON: if csvPF or not FJQC.formatJSON:
entityPerformActionNumItems([Ent.USER, userId], jcount, entityType, i, count) entityPerformActionNumItems([Ent.USER, userId], jcount, entityType, i, count)
if jcount == 0: if jcount == 0:
continue continue
j = 0 for courseId, courseInfo in coursesInfo.items():
for courseId in courseIds:
j += 1 j += 1
courseId = addCourseIdScope(courseId)
courseInfo = coursesInfo[courseId]
if not courseInfo:
continue
courseNameId = f'{courseInfo["name"]} ({courseId})' courseNameId = f'{courseInfo["name"]} ({courseId})'
try: try:
invitation = callGAPI(courseInfo['croom'].invitations(), 'create', invitation = callGAPI(courseInfo['croom'].invitations(), 'create',
throwReasons=[GAPI.NOT_FOUND, GAPI.FAILED_PRECONDITION, GAPI.ALREADY_EXISTS, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED], throwReasons=[GAPI.NOT_FOUND, GAPI.FAILED_PRECONDITION, GAPI.ALREADY_EXISTS, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
body={'userId': userId, 'courseId': courseId, 'role': role}) body={'userId': userId, 'courseId': courseId, 'role': role})
invitation['courseName'] = courseInfo['name']
invitation['userEmail'] = userEmail
if not csvPF: if not csvPF:
if not FJQC.formatJSON: if not FJQC.formatJSON:
Ind.Increment() Ind.Increment()
@@ -47274,14 +47274,12 @@ def createClassroomInvitations(users):
printLine(json.dumps(cleanJSON(invitation), ensure_ascii=False, sort_keys=True)) printLine(json.dumps(cleanJSON(invitation), ensure_ascii=False, sort_keys=True))
else: else:
if not FJQC.formatJSON: if not FJQC.formatJSON:
invitation['courseName'] = courseInfo['name']
invitation['userEmail'] = userEmail
csvPF.WriteRow(invitation) csvPF.WriteRow(invitation)
else: else:
csvPF.WriteRowNoFilter({'userEmail': userEmail, csvPF.WriteRowNoFilter({'userEmail': userEmail,
'JSON': json.dumps(cleanJSON(invitation), ensure_ascii=False, sort_keys=True)}) 'JSON': json.dumps(cleanJSON(invitation), ensure_ascii=False, sort_keys=True)})
except GAPI.permissionDenied: except GAPI.permissionDenied as e:
entityUnknownWarning(Ent.USER, userId, i, count) entityActionFailedWarning([Ent.USER, userId, Ent.COURSE, courseNameId, entityType, None], str(e), j, jcount)
break break
except GAPI.notFound: except GAPI.notFound:
entityUnknownWarning(Ent.COURSE, courseNameId, j, jcount) entityUnknownWarning(Ent.COURSE, courseNameId, j, jcount)
@@ -47422,6 +47420,49 @@ def printShowClassroomInvitations(users):
if csvPF: if csvPF:
csvPF.writeCSVfile('ClassroomInvitations') csvPF.writeCSVfile('ClassroomInvitations')
# gam delete classroominvitation courses <CourseEntity> (ids <ClassroomInvitationIDEntity>)|(role all|owner|student|teacher)
def doDeleteClassroomInvitations():
croom = buildGAPIObject(API.CLASSROOM)
courseIdList = invitationIds = None
role = CLASSROOM_ROLE_ALL
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if myarg in {'course', 'courses', 'class', 'classes'}:
courseIdList = getEntityList(Cmd.OB_COURSE_ENTITY, shlexSplit=True)
elif myarg in {'id', 'ids'}:
invitationIds = getEntityList(Cmd.OB_CLASSROOM_INVITATION_ID_ENTITY)
elif myarg == 'role':
role = getChoice(CLASSROOM_ROLE_MAP, mapChoice=True)
else:
unknownArgumentExit()
if courseIdList is None:
missingArgumentExit('courses <CourseEntity>')
entityType = Ent.CLASSROOM_INVITATION
i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, True)
for courseId, courseInfo in coursesInfo.items():
i += 1
courseNameId = f'{courseInfo["name"]} ({courseId})'
if invitationIds is not None:
userInvitationIds = invitationIds
else:
status, userInvitationIds = _getClassroomInvitationIds(courseInfo['croom'], None, [courseId], role, i, count)
if status < 0:
continue
jcount = len(userInvitationIds)
entityPerformActionNumItems([Ent.COURSE, courseNameId], jcount, entityType, i, count)
Ind.Increment()
j = 0
for invitationId in userInvitationIds:
j += 1
try:
callGAPI(courseInfo['croom'].invitations(), 'delete',
throwReasons=[GAPI.NOT_FOUND, GAPI.FAILED_PRECONDITION, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
id=invitationId)
entityActionPerformed([Ent.COURSE, courseNameId, entityType, invitationId], j, jcount)
except (GAPI.notFound, GAPI.failedPrecondition, GAPI.forbidden, GAPI.permissionDenied) as e:
entityActionFailedWarning([Ent.COURSE, courseNameId, entityType, invitationId], str(e), j, jcount)
Ind.Decrement()
# gam show classroominvitations (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>]) # gam show classroominvitations (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
# [role all|owner|student|teacher] [formatjson] # [role all|owner|student|teacher] [formatjson]
# gam print classroominvitations [todrive <ToDriveAttribute>*] (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>]) # gam print classroominvitations [todrive <ToDriveAttribute>*] (course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
@@ -51304,6 +51345,11 @@ def _mapDriveRevisionNames(revision):
revision['lastModifyingUserName'] = revision['lastModifyingUser']['displayName'] revision['lastModifyingUserName'] = revision['lastModifyingUser']['displayName']
_mapDriveUser(revision['lastModifyingUser']) _mapDriveUser(revision['lastModifyingUser'])
DRIVEFILE_BASIC_PERMISSION_FIELDS = [
'displayName', 'id', 'emailAddress', 'domain', 'role', 'type',
'allowFileDiscovery', 'expirationTime', 'deleted', 'permissionDetails' #permissionDetails must be last
]
DRIVE_FIELDS_CHOICE_MAP = { DRIVE_FIELDS_CHOICE_MAP = {
'alternatelink': 'webViewLink', 'alternatelink': 'webViewLink',
'appdatacontents': 'spaces', 'appdatacontents': 'spaces',
@@ -51362,8 +51408,9 @@ DRIVE_FIELDS_CHOICE_MAP = {
'ownernames': 'owners.displayName', 'ownernames': 'owners.displayName',
'owners': 'owners', 'owners': 'owners',
'parents': 'parents', 'parents': 'parents',
'permissions': 'permissions', 'permissiondetails': 'permissions.permissionDetails',
'permissionids': 'permissionIds', 'permissionids': 'permissionIds',
'permissions': 'permissions',
'properties': 'properties', 'properties': 'properties',
'quotabytesused': 'quotaBytesUsed', 'quotabytesused': 'quotaBytesUsed',
'quotaused': 'quotaBytesUsed', 'quotaused': 'quotaBytesUsed',
@@ -51719,7 +51766,7 @@ def showFileInfo(users):
showLabels = getChoice(SHOWLABELS_CHOICES) showLabels = getChoice(SHOWLABELS_CHOICES)
elif myarg == 'showshareddrivepermissions': elif myarg == 'showshareddrivepermissions':
getPermissionsForSharedDrives = True getPermissionsForSharedDrives = True
permissionsFields = 'nextPageToken,permissions' permissionsFields = f'nextPageToken,permissions({",".join(DRIVEFILE_BASIC_PERMISSION_FIELDS)})'
elif myarg == 'returnidonly': elif myarg == 'returnidonly':
returnIdOnly = True returnIdOnly = True
elif DFF.ProcessArgument(myarg): elif DFF.ProcessArgument(myarg):
@@ -51727,6 +51774,7 @@ def showFileInfo(users):
else: else:
FJQC.GetFormatJSON(myarg) FJQC.GetFormatJSON(myarg)
if DFF.fieldsList: if DFF.fieldsList:
if not getPermissionsForSharedDrives:
getPermissionsForSharedDrives, permissionsFields = _setGetPermissionsForSharedDrives(DFF.fieldsList) getPermissionsForSharedDrives, permissionsFields = _setGetPermissionsForSharedDrives(DFF.fieldsList)
_setSelectionFields() _setSelectionFields()
fields = getFieldsFromFieldsList(DFF.fieldsList) fields = getFieldsFromFieldsList(DFF.fieldsList)
@@ -52581,6 +52629,7 @@ DRIVEFILE_ACL_ROLES_MAP = {
} }
DRIVEFILE_ACL_PERMISSION_TYPES = ['anyone', 'domain', 'group', 'user'] # anyone must be first element DRIVEFILE_ACL_PERMISSION_TYPES = ['anyone', 'domain', 'group', 'user'] # anyone must be first element
DRIVEFILE_ACL_PERMISSION_DETAILS_TYPES = ['file', 'member']
class PermissionMatch(): class PermissionMatch():
_PERMISSION_MATCH_ACTION_MAP = {'process': True, 'skip': False} _PERMISSION_MATCH_ACTION_MAP = {'process': True, 'skip': False}
@@ -52667,10 +52716,13 @@ class PermissionMatch():
self.permissionFields.add('expirationTime') self.permissionFields.add('expirationTime')
elif myarg == 'deleted': elif myarg == 'deleted':
deletedLocation = Cmd.Location() deletedLocation = Cmd.Location()
body['deleted'] = getBoolean() body[myarg] = getBoolean()
self.permissionFields.add('deleted') self.permissionFields.add('deleted')
elif myarg == 'inherited': elif myarg == 'inherited':
body['inherited'] = getBoolean() body[myarg] = getBoolean()
self.permissionFields.add('permissionDetails')
elif myarg == 'permtype':
body['permissionType'] = getChoice(DRIVEFILE_ACL_PERMISSION_DETAILS_TYPES)
self.permissionFields.add('permissionDetails') self.permissionFields.add('permissionDetails')
elif myarg in {'em', 'endmatch'}: elif myarg in {'em', 'endmatch'}:
break break
@@ -52710,21 +52762,27 @@ class PermissionMatch():
if field in {'type', 'role'}: if field in {'type', 'role'}:
if permission.get(field, '') not in value: if permission.get(field, '') not in value:
break break
elif field in {'nottype'}: elif field == 'nottype':
if permission.get('type', '') in value: if permission.get('type', '') in value:
break break
elif field in {'notrole'}: elif field == 'notrole':
if permission.get('role', '') in value: if permission.get('role', '') in value:
break break
elif field in {'allowFileDiscovery', 'deleted'}: elif field in {'allowFileDiscovery', 'deleted'}:
if value != permission.get(field, False): if value != permission.get(field, False):
break break
elif field in {'inherited'}: elif field == 'inherited':
if 'permissionDetails' in permission: if 'permissionDetails' in permission:
if value != permission['permissionDetails'][0].get(field, False): if value != permission['permissionDetails'][0].get(field, False):
break break
else: else:
break break
elif field == 'permissionType':
if 'permissionDetails' in permission:
if value != permission['permissionDetails'][0].get(field, ''):
break
else:
break
elif field in {'expirationstart', 'expirationend'}: elif field in {'expirationstart', 'expirationend'}:
if 'expirationTime' in permission: if 'expirationTime' in permission:
expirationDateTime, _ = iso8601.parse_date(permission['expirationTime']) expirationDateTime, _ = iso8601.parse_date(permission['expirationTime'])
@@ -52736,14 +52794,14 @@ class PermissionMatch():
break break
else: else:
break break
elif field in {'emailaddresslist'}: elif field == 'emailaddresslist':
emailAddress = permission.get('emailAddress') emailAddress = permission.get('emailAddress')
if emailAddress: if emailAddress:
if emailAddress not in value: if emailAddress not in value:
break break
else: else:
break break
elif field in {'permissionidlist'}: elif field == 'permissionidlist':
permissionId = permission.get('id') permissionId = permission.get('id')
if permissionId: if permissionId:
if permissionId not in value: if permissionId not in value:
@@ -53141,6 +53199,16 @@ def printFileList(users):
if DLP.onlySharedDrives or getPermissionsForSharedDrives or DFF.showSharedDriveNames: if DLP.onlySharedDrives or getPermissionsForSharedDrives or DFF.showSharedDriveNames:
_setSkipObjects(skipObjects, ['driveId'], DFF.fieldsList) _setSkipObjects(skipObjects, ['driveId'], DFF.fieldsList)
def _printFileInfoRow(baserow, fileInfo):
row = baserow.copy()
if not FJQC.formatJSON:
csvPF.WriteRowTitles(flattenJSON(fileInfo, flattened=row, skipObjects=skipObjects, timeObjects=timeObjects,
simpleLists=simpleLists, delimiter=delimiter))
else:
row['JSON'] = json.dumps(cleanJSON(fileInfo, skipObjects=skipObjects, timeObjects=timeObjects),
ensure_ascii=False, sort_keys=True)
csvPF.WriteRowTitlesJSONNoFilter(row)
def _printFileInfo(drive, user, f_file, cleanFileName): def _printFileInfo(drive, user, f_file, cleanFileName):
driveId = f_file.get('driveId') driveId = f_file.get('driveId')
checkSharedDrivePermissions = getPermissionsForSharedDrives and driveId and 'permissions' not in f_file checkSharedDrivePermissions = getPermissionsForSharedDrives and driveId and 'permissions' not in f_file
@@ -53218,18 +53286,15 @@ def printFileList(users):
baserow[fileNameTitle] = fileInfo[fileNameTitle] baserow[fileNameTitle] = fileInfo[fileNameTitle]
if 'owners' in fileInfo: if 'owners' in fileInfo:
flattenJSON({'owners': fileInfo['owners']}, flattened=baserow, skipObjects=skipObjects) flattenJSON({'owners': fileInfo['owners']}, flattened=baserow, skipObjects=skipObjects)
permissions = fileInfo.pop('permissions') for permission in fileInfo.pop('permissions'):
for permission in permissions:
row = baserow.copy()
fileInfo['permission'] = permission fileInfo['permission'] = permission
if not FJQC.formatJSON: pdetails = fileInfo['permission'].pop('permissionDetails', [])
csvPF.WriteRowTitles(flattenJSON(fileInfo, flattened=row, skipObjects=skipObjects, timeObjects=timeObjects, if not pdetails:
simpleLists=simpleLists, delimiter=delimiter)) _printFileInfoRow(baserow, fileInfo)
else: else:
row = baserow.copy() for pdetail in pdetails:
row['JSON'] = json.dumps(cleanJSON(fileInfo, skipObjects=skipObjects, timeObjects=timeObjects), fileInfo['permission']['permissionDetails'] = pdetail
ensure_ascii=False, sort_keys=True) _printFileInfoRow(baserow, fileInfo)
csvPF.WriteRowTitlesJSONNoFilter(row)
else: else:
if not countsRowFilter: if not countsRowFilter:
csvPF.UpdateMimeTypeCounts(flattenJSON(fileInfo, flattened=row, skipObjects=skipObjects, timeObjects=timeObjects, csvPF.UpdateMimeTypeCounts(flattenJSON(fileInfo, flattened=row, skipObjects=skipObjects, timeObjects=timeObjects,
@@ -53417,7 +53482,7 @@ def printFileList(users):
showLabels = getChoice(SHOWLABELS_CHOICES) showLabels = getChoice(SHOWLABELS_CHOICES)
elif myarg == 'showshareddrivepermissions': elif myarg == 'showshareddrivepermissions':
getPermissionsForSharedDrives = True getPermissionsForSharedDrives = True
permissionsFields = 'nextPageToken,permissions' permissionsFields = f'nextPageToken,permissions({",".join(DRIVEFILE_BASIC_PERMISSION_FIELDS)})'
elif myarg == 'pmfilter': elif myarg == 'pmfilter':
pmselect = False pmselect = False
elif myarg == 'oneitemperrow': elif myarg == 'oneitemperrow':
@@ -53459,8 +53524,9 @@ def printFileList(users):
DLP.Finalize(fileIdEntity) DLP.Finalize(fileIdEntity)
if DLP.PM.permissionMatches: if DLP.PM.permissionMatches:
getPermissionsForSharedDrives = True getPermissionsForSharedDrives = True
permissionsFields = 'nextPageToken,permissions' permissionsFields = f'nextPageToken,permissions({",".join(DRIVEFILE_BASIC_PERMISSION_FIELDS)})'
elif DFF.fieldsList: elif DFF.fieldsList:
if not getPermissionsForSharedDrives:
getPermissionsForSharedDrives, permissionsFields = _setGetPermissionsForSharedDrives(DFF.fieldsList) getPermissionsForSharedDrives, permissionsFields = _setGetPermissionsForSharedDrives(DFF.fieldsList)
if DFF.fieldsList: if DFF.fieldsList:
_setSelectionFields() _setSelectionFields()
@@ -60875,24 +60941,19 @@ def infoDriveFileACLs(users, useDomainAdminAccess=False):
def doInfoDriveFileACLs(): def doInfoDriveFileACLs():
infoDriveFileACLs([_getAdminEmail()], True) infoDriveFileACLs([_getAdminEmail()], True)
DRIVEFILE_BASIC_PERMISSION_FIELDS = [
'displayName', 'id', 'emailAddress', 'domain', 'role', 'type',
'allowFileDiscovery', 'expirationTime', 'deleted'
]
DRIVEFILE_PERMISSIONS_FOR_VIEW_CHOICES = ['published'] DRIVEFILE_PERMISSIONS_FOR_VIEW_CHOICES = ['published']
def getDriveFilePermissionsFields(myarg, fieldsList): def getDriveFilePermissionsFields(myarg, fieldsList):
if myarg in DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP: if myarg in DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP:
fieldsList.append(DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP[myarg]) fieldsList.append(DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP[myarg])
elif myarg == 'basicpermissions': elif myarg == 'basicpermissions':
fieldsList.extend(DRIVEFILE_BASIC_PERMISSION_FIELDS) fieldsList.extend(DRIVEFILE_BASIC_PERMISSION_FIELDS[:-1])
elif myarg == 'fields': elif myarg == 'fields':
for field in _getFieldsList(): for field in _getFieldsList():
if field in DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP: if field in DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP:
fieldsList.append(DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP[field]) fieldsList.append(DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP[field])
elif field == 'basicpermissions': elif field == 'basicpermissions':
fieldsList.extend(DRIVEFILE_BASIC_PERMISSION_FIELDS) fieldsList.extend(DRIVEFILE_BASIC_PERMISSION_FIELDS[:-1])
else: else:
invalidChoiceExit(field, DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP, True) invalidChoiceExit(field, DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP, True)
else: else:
@@ -60916,6 +60977,17 @@ def getDriveFilePermissionsFields(myarg, fieldsList):
# (orderby <DriveFileOrderByFieldName> [ascending|descending])* # (orderby <DriveFileOrderByFieldName> [ascending|descending])*
# [formatjson] [adminaccess|asadmin] # [formatjson] [adminaccess|asadmin]
def printShowDriveFileACLs(users, useDomainAdminAccess=False): def printShowDriveFileACLs(users, useDomainAdminAccess=False):
def _printPermissionRow(baserow, permission):
row = baserow.copy()
flattenJSON({'permission': permission}, flattened=row, timeObjects=timeObjects)
if not FJQC.formatJSON:
csvPF.WriteRowTitles(row)
elif csvPF.CheckRowTitles(row):
row = baserow.copy()
row['JSON'] = json.dumps(cleanJSON({'permission': permission}, timeObjects=timeObjects),
ensure_ascii=False, sort_keys=True)
csvPF.WriteRowNoFilter(row)
csvPF = CSVPrintFile(['Owner', 'id'], 'sortall') if Act.csvFormat() else None csvPF = CSVPrintFile(['Owner', 'id'], 'sortall') if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF) FJQC = FormatJSONQuoteChar(csvPF)
fileIdEntity = getDriveFileEntity() fileIdEntity = getDriveFileEntity()
@@ -61036,16 +61108,14 @@ def printShowDriveFileACLs(users, useDomainAdminAccess=False):
baserow[fileNameTitle] = fileName baserow[fileNameTitle] = fileName
if oneItemPerRow: if oneItemPerRow:
for permission in permissions: for permission in permissions:
row = baserow.copy()
_mapDrivePermissionNames(permission) _mapDrivePermissionNames(permission)
flattenJSON({'permission': permission}, flattened=row, timeObjects=timeObjects) pdetails = permission.pop('permissionDetails', [])
if not FJQC.formatJSON: if not pdetails:
csvPF.WriteRowTitles(row) _printPermissionRow(baserow, permission)
elif csvPF.CheckRowTitles(row): else:
row = baserow.copy() for pdetail in pdetails:
row['JSON'] = json.dumps(cleanJSON({'permission': permission}, timeObjects=timeObjects), permission['permissionDetails'] = pdetail
ensure_ascii=False, sort_keys=True) _printPermissionRow(baserow, permission)
csvPF.WriteRowNoFilter(row)
else: else:
row = baserow.copy() row = baserow.copy()
for permission in permissions: for permission in permissions:
@@ -62287,6 +62357,17 @@ SHOW_NO_PERMISSIONS_DRIVES_CHOICE_MAP = {
# [shownopermissionsdrives false|true|only] # [shownopermissionsdrives false|true|only]
# [formatjsn] # [formatjsn]
def printShowSharedDriveACLs(users, useDomainAdminAccess=False): def printShowSharedDriveACLs(users, useDomainAdminAccess=False):
def _printPermissionRow(baserow, permission):
row = baserow.copy()
flattenJSON({'permission': permission}, flattened=row, timeObjects=timeObjects)
if not FJQC.formatJSON:
csvPF.WriteRowTitles(row)
elif csvPF.CheckRowTitles(row):
row = baserow.copy()
row['JSON'] = json.dumps(cleanJSON({'permission': permission}, timeObjects=timeObjects),
ensure_ascii=False, sort_keys=True)
csvPF.WriteRowNoFilter(row)
csvPF = CSVPrintFile(['User', 'id', 'name', 'createdTime'], 'sortall') if Act.csvFormat() else None csvPF = CSVPrintFile(['User', 'id', 'name', 'createdTime'], 'sortall') if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF) FJQC = FormatJSONQuoteChar(csvPF)
roles = set() roles = set()
@@ -62469,16 +62550,14 @@ def printShowSharedDriveACLs(users, useDomainAdminAccess=False):
baserow = {'User': user, 'id': shareddrive['id'], 'name': shareddrive['name'], 'createdTime': shareddrive['createdTime']} baserow = {'User': user, 'id': shareddrive['id'], 'name': shareddrive['name'], 'createdTime': shareddrive['createdTime']}
if shareddrive['permissions']: if shareddrive['permissions']:
for permission in shareddrive['permissions']: for permission in shareddrive['permissions']:
row = baserow.copy()
_mapDrivePermissionNames(permission) _mapDrivePermissionNames(permission)
flattenJSON({'permission': permission}, flattened=row, timeObjects=timeObjects) pdetails = permission.pop('permissionDetails', [])
if not FJQC.formatJSON: if not pdetails:
csvPF.WriteRowTitles(row) _printPermissionRow(baserow, permission)
elif csvPF.CheckRowTitles(row): else:
row = baserow.copy() for pdetail in pdetails:
row['JSON'] = json.dumps(cleanJSON({'permission': permission}, timeObjects=timeObjects), permission['permissionDetails'] = pdetail
ensure_ascii=False, sort_keys=True) _printPermissionRow(baserow, permission)
csvPF.WriteRowNoFilter(row)
else: else:
if not FJQC.formatJSON: if not FJQC.formatJSON:
csvPF.WriteRowTitles(baserow) csvPF.WriteRowTitles(baserow)
@@ -71448,6 +71527,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
Cmd.ARG_CHROMENETWORK: doDeleteChromeNetwork, Cmd.ARG_CHROMENETWORK: doDeleteChromeNetwork,
Cmd.ARG_CHROMEPOLICY: doDeleteChromePolicy, Cmd.ARG_CHROMEPOLICY: doDeleteChromePolicy,
Cmd.ARG_CIGROUP: doDeleteCIGroups, Cmd.ARG_CIGROUP: doDeleteCIGroups,
Cmd.ARG_CLASSROOMINVITATION: doDeleteClassroomInvitations,
Cmd.ARG_CONTACT: doDeleteDomainContacts, Cmd.ARG_CONTACT: doDeleteDomainContacts,
Cmd.ARG_CONTACTPHOTO: doDeleteDomainContactPhoto, Cmd.ARG_CONTACTPHOTO: doDeleteDomainContactPhoto,
Cmd.ARG_COURSE: doDeleteCourse, Cmd.ARG_COURSE: doDeleteCourse,