Multiple updates (#1273)

* Multiple updates

Add member to print cigroups|cigroup-members to select groups to display
Drop Google-Coordinate product ID
Update print|show driveactivity to Drive Activity API v2
Check for more parents than 1 in create|update drivefile
Update documentation
Allow times_to_check_status with gam getcommand cros
Display deviceId and commandId when issuing/getting commands

* Fix orgunit references in vault

* Rename member to enterprisemember in print cigroups|cigroup-members

Give error message indication the Enterprise license is required

* Add lastKnownNetwork to CrOS fields

* Soft fail when deleting user photo

* Fix bug in PR #1273
This commit is contained in:
Ross Scroggs
2020-12-05 18:54:26 -08:00
committed by GitHub
parent 6a879927a7
commit 8ce18960fe
7 changed files with 328 additions and 185 deletions

View File

@ -73,17 +73,9 @@ If an item contains spaces, it should be surrounded by ".
<ProductID> ::=
Google-Apps|
Google-Chrome-Device-Management|
Google-Coordinate|
Google-Drive-storage|
Google-Vault|
101001|101005|101031
<ProductID> ::=
Google-Apps|
Google-Chrome-Device-Management|
Google-Coordinate|
Google-Drive-storage|
Google-Vault|
101001|101005|101006|101031|101033|101034
101001|101005|101031|101033|101034
<SKUID> ::=
cloudidentity|identity|1010010001|
cloudidentitypremium|identitypremium|1010050001|
@ -103,7 +95,6 @@ If an item contains spaces, it should be surrounded by ".
gsbau|businessarchived|gsuitebusinessarchived|
gseau|enterprisearchived|gsuiteenterprisearchived|
chrome|cdm|googlechromedevicemanagement|Google-Chrome-Device-Management|
coordinate|googlecoordinate|Google-Coordinate|
wsess|workspaceesentials|gsuiteessentials|essentials|d4e|driveenterprise|drive4enterprise|1010060001|
wsentess|workspaceenterpriseessentials|1010060003|
drive20gb|20gb|googledrivestorage20gb|Google-Drive-storage-20GB|
@ -209,10 +200,6 @@ If an item contains spaces, it should be surrounded by ".
<ParameterValue> ::= <String>
<Password> ::= <String>
<PermissionID> ::= id:<String>|<EmailAddress>|anyone|anyonewithlink
<PrinterID> ::= <String>
<PrintJobAge> ::= <Number>[m|h|d]
<PrintJobID> ::= <String>
<PrintJobStatus> ::= done|error|held|in_progress|queued|submitted
<PropertyKey> ::= <String>
<PropertyValue> ::= <String>
<QueryCalendar> ::= <String>
@ -222,7 +209,6 @@ If an item contains spaces, it should be surrounded by ".
<QueryGmail> ::= <String> See: https://support.google.com/mail/answer/7190
<QueryGroup> ::= <String> See: https://developers.google.com/admin-sdk/directory/v1/guides/search-groups
<QueryMobile> ::= <String> See: https://support.google.com/a/answer/7549103
<QueryPrinter> ::= <String> See: https://developers.google.com/cloud-print/docs/appInterfaces#search
<QueryPrintJob> ::= <String> See: https://developers.google.com/cloud-print/docs/appInterfaces#parameters_3
<QueryUser> ::= <String> See: https://developers.google.com/admin-sdk/directory/v1/guides/search-users
<QueryVaultCorpus> ::= <String> See: https://developers.google.com/vault/reference/rest/v1/matters.holds#CorpusQuery
@ -594,12 +580,10 @@ Items, separated by spaces, with spaces, commas or single quotes in the items th
<MembersFieldNameList> ::= "<MembersFieldName>(,<MembersFieldName>)*"
<MobileList> ::= "<MobileId>(,<MobileId>)*"
<OrgUnitList> ::= "<OrgUnitPath>(,<OrgUnitPath>)*"
<PrinterIDList> ::= "<PrinterID>(,<PrinterID>)*"
<ProductIDList> ::= "(<ProductID>|SKUID>)(,<ProductID>|SKUID>)*"
<PrintJobIDList> ::= "<PrintJobID>(,<PrintJobID>)*"
<QueryCrOSList> ::= "<QueryCrOS>(,<QueryCrOS>)*"
<QueryMobileList> ::= "<QueryMobile>(,<QueryMobile>)*"
<QueryPrinterList> ::= "<QueryPrinter>(,<QueryPrinter>)*"
<QueryUserList> ::= "<QueryUser>(,<QueryUser>)*"
<ResourceIDList> ::= "<ResourceID>(,<ResourceID>)*"
<SKUIDList> ="<SKUID>(,<SKUID>)*"
@ -653,7 +637,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
## Item attributes
<BuildingAttributes> ::=
<BuildingAttribute> ::=
(description <String>)|
(floors <FloorNameList>)|
(id <String>)|
@ -661,7 +645,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
(longitude <Float>)|
(name <String>)
<CalendarAttributes> ::=
<CalendarAttribute> ::=
(selected <Boolean>)|(hidden <Boolean>)|(summary <String>)|(colorindex|colorid <CalendarColorIndex>)|(backgroundcolor <ColorValue>)|(foregroundcolor <ColorValue>)|
(reminder clear|(email|sms|pop <Number>))|
(notification clear|(email|sms eventcreation|eventchange|eventcancellation|eventresponse|agenda))
@ -669,7 +653,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
<CalendarSettings> ::=
(summary <String>)|(description <String>)|(location <String>)|(timezone <TimeZone>)
<CourseAttributes> ::=
<CourseAttribute> ::=
(description <String>)|
(heading <String>)|
(name <String>)|
@ -678,14 +662,18 @@ Specify a collection of Users by directly specifying them or by specifiying item
(state|status <CourseState>)|
(owner|ownerid|teacher <UserItem>)
<CrOSAttributes> ::=
<CrOSAttribute> ::=
(asset|assetid|tag <String>)|
(location <String>)|
(notes <String>)|
(org|ou <OrgUnitPath>)|
(user <Name>)
<DriveFileAddAttributes> ::=
<CIGroupAttribute> ::=
(description <String>)|
(name <String>)
<DriveFileAddAttribute> ::=
(localfile <FileName>)|
(convert)|(ocr)|(ocrlanguage <Language>)|
(restricted|restrict)|(starred|star)|(trashed|trash)|(viewed|view)|
@ -695,7 +683,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
(lastviewedbyme <Time>)|(modifieddate|modifiedtime <Time>)|(description <String>)|(mimetype <MimeType>)|
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|
(shortcut <DriveFileID>)
<DriveFileUpdateAttributes> ::=
<DriveFileUpdateAttribute> ::=
(localfile <FileName>)|
(convert)|(ocr)|(ocrlanguage <Language>)|
(restricted|restrict <Boolean>)|(starred|star <Boolean>)|(trashed|trash <Boolean>)|(viewed|view <Boolean>)|
@ -773,13 +761,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
<MobileAction> ::=
admin_remote_wipe|wipe|admin_account_wipe|accountwipe|wipeaccount|approve|block|cancel_remote_wipe_then_activate|cancel_remote_wipe_then_block
<PrinterAttributes> ::= (currentquota <Number>)|(dailyquota <Number>)|
(defaultdisplayname <String>)|(description <String>)|(displayname <String>)|(firmware <String>)|(gcpversion <String>)|
(istosaccepted <Boolean>)|(manufacturer <String>)|(model <String>)|(name <String>)|(ownerid <EmailAddress>)|(proxy <String>)|(public <Boolean>)|
(quotaenabled <Boolean>)|(status <Number>)|(type <String>)|(uuid <String>)|
(setupurl <URL>)|(supporturl <URL>)|(updateurl <URL>)
<ResourceAttributes> ::=
<ResourceAttribute> ::=
(buildingid <BuildingID>)|
(capacity <Number>)|
(category other|room|conference_room|category_unknown)|
@ -813,7 +795,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
(recoveryphone <string>)|
(suspended <Boolean>)|
(<SchemaName>.<FieldName> [multivalued|multivalue|value|multinonempty [type home|other|work|(custom <String>)]] <String>)
<UserMultiAttributes> ::=
<UserMultiAttribute> ::=
(address clear|(type home|other|work|(custom <String>) [unstructured|formatted <String>] [pobox <String>] [extendedaddress <String>] [streetaddress <String>]
[locality <String>] [region <String>] [postalcode <String>] [country <String>] [countrycode <String>] notprimary|primary))|
(otheremail clear|(home|other|work|<String> <String>))|
@ -872,7 +854,7 @@ gam <UserTypeEntity> check serviceaccount [scope|scopes <APIScopeURLList>]
gam whatis <EmailItem>
<ResoldCustomerAttributes> ::=
<ResoldCustomerAttribute> ::=
(email|alternateemail <EmailAddress>)|
(contact|contactname <String>)|
(phone|phonenumber <String>)|
@ -885,7 +867,7 @@ gam whatis <EmailItem>
(zipcode|postal|postalcode <String>)|
(country|countrycode <String>)
gam create resoldcustomer <CustomerDomain> (customer_auth_token <String>) <ResoldCustomerAttributes>+
gam create resoldcustomer <CustomerDomain> (customer_auth_token <String>) <ResoldCustomerAttribute>+
gam update resoldcustomer <CustomerID> [customer_auth_token <String>] <ResoldCustomerAttribues>+
gam info resoldcustomer <CustomerID>
@ -976,7 +958,7 @@ gam delete domainalias|aliasdomain <DomainAlias>
gam info domainalias|aliasdomain <DomainAlias>
gam print domainaliases|aliasdomains [todrive]
<CustomerAttributes> ::=
<CustomerAttribute> ::=
(primary <DomainName>)|
(adminsecondaryemail|alternateemail <EmailAddress>)|
(contact|contactname <String>)|
@ -991,7 +973,7 @@ gam print domainaliases|aliasdomains [todrive]
(zipcode|postal|postalcode <String>)|
(country|countrycode <String>)
gam update customer <CustomerAttributes>*
gam update customer <CustomerAttribute>*
gam info customer
@ -1034,7 +1016,7 @@ The following attributes are equivalent:
sendnotifications false - sendupdates none
sendnotifications true - sendupdates all
<EventAttributes> ::=
<EventAttribute> ::=
anyonecanaddself|
(attendee <EmailAddress>)|
available|
@ -1060,8 +1042,8 @@ The following attributes are equivalent:
(timezone <Timezone>)|
(visibility default|public|prvate)
<EventUpdateAttributes> ::=
<EventAttributes>|
<EventUpdateAttribute> ::=
<EventAttribute>|
(removeattendee <EmailAddress>)|
(replacedescription <RegularExpression> <String>)
@ -1076,10 +1058,10 @@ The following attributes are equivalent:
<EventDisplayProperty> ::=
(timezone <TimeZone>)
gam calendar <CalendarItem> addevent [id <String>] <EventAttributes>+ [<EventNotificationAttribute>]
gam calendar <CalendarItem> addevent [id <String>] <EventAttribute>+ [<EventNotificationAttribute>]
gam calendar <CalendarItem> deleteevent id|eventid <EventID> [doit] [<EventNotificationAttribute>]
gam calendar <CalendarItem> moveevent id|eventid <EventID> [doit] [<EventNotificationAttribute>]
gam calendar <CalendarItem> updateevent <EventID> <EventUpdateAttributes>+ [<EventNotificationAttribute>]
gam calendar <CalendarItem> updateevent <EventID> <EventUpdateAttribute>+ [<EventNotificationAttribute>]
gam calendar <CalendarItem> wipe
gam calendar <CalendarItem> printevents <EventSelectProperty>* <EventDisplayProperty>* [todrive]
@ -1099,7 +1081,19 @@ gam calendar <CalendarItem> modify <CalendarSettings>+
disable|
reenable
gam update cros <CrOSEntity> (<CrOSAttributes>+)|(action <CrOSAction> [acknowledge_device_touch_requirement])
gam update cros <CrOSEntity> action <CrOSAction> [acknowledge_device_touch_requirement]
<CrOSCommand>
wipe_users|
remote_powerwash|
reboot|
set_volume <0-100>|
take_a_screenshot
gam issuecommand cros <CrOSEntity> command <CrOSCommand> [times_to_check_status <0-1000+>] [doit]
gam getcommand cros <CrOSEntity> commandid <CommandID> [times_to_check_status <0-1000+>]
gam update cros <CrOSEntity> <CrOSAttribute>+
gam info cros <CrOSEntity> [nolists] [listlimit <Number>] [start <Date>] [end <Date>]
[basic|full|allfields] <CrOSFieldName>* [fields <CrOSFieldNameList>] [downloadfile latest|<Time>] [targetfolder <FilePath>]
@ -1181,8 +1175,29 @@ gam info mobile <MobileID>
gam print mobile [todrive] [(query <QueryMobile>)|(queries <QueryMobileList>)] [basic|full] [orderby <MobileOrderByFieldName> [ascending|descending]]
fields <MobileFieldNameList>] [delimiter <Character>] [appslimit <Number>] [listlimit <Number>]
gam create group <EmailAddress> <GroupAttributes>*
gam update group <GroupItem> [email <EmailAddress>] <GroupAttributes>*
gam create cigroup <EmailAddress> <CIGroupAttribute>*
[makeowner] [alias|aliases <AliasList>] [dynamic <QueryDynamicGroup>]
gam update cigroup <GroupItem> [email <EmailAddress>] <CIGroupAttribute>* [security]
gam update cigroup <GroupItem> add [owner|manager|member] [notsuspended|suspended] [expiretime <Time>] <UserTypeEntity>
gam update cigroup <GroupItem> delete|remove [owner|manager|member] [notsuspended|suspended] <UserTypeEntity>
gam update cigroup <GroupItem> sync [owner|manager|member] [notsuspended|suspended] [expiretime <Time>] <UserTypeEntity>
gam update cigroup <GroupItem> update [owner|manager|member] [notsuspended|suspended] [expiretime <Time>] <UserTypeEntity>
gam update cigroup <GroupItem> clear [member] [manager] [owner] [notsuspended|suspended]
gam delete cigroup <GroupItem>
gam info cigroup <GroupItem> [nousers] [nojoindate] [showupdatedate]
gam print cigroups [todrive]
[enterprisemember <UserItem>]
[members|memberscount] [managers|managerscount] [owners|ownerscount]
[delimiter <Character>] [sortheaders]
gam info cimember <UserItem> <GroupItem>
gam print cigroup-members|cigroups-members [todrive]
[(enterprisemember <UserItem>)|(cigroup <GroupItem>)]
[roles <GroupRoleList>]
gam create group <EmailAddress> <GroupAttribute>*
gam update group <GroupItem> [email <EmailAddress>] <GroupAttribute>*
gam update group <GroupItem> add [owner|manager|member] [notsuspended|suspended] [allmail|daily|digest|none|nomail] <UserTypeEntity>
gam update group <GroupItem> delete|remove [owner|manager|member] <UserTypeEntity>
gam update group <GroupItem> sync [owner|manager|member] [notsuspended|suspended] [allmail|daily|digest|none|nomail] <UserTypeEntity>
@ -1205,8 +1220,8 @@ gam print group-members|groups-members [todrive]
gam print licenses [todrive] [(products|product <ProductIDList>)|(skus|sku <SKUIDList>)|allskus|gsuite] [countsonly]
gam show license|licenses|licence|licences [(products|product <ProductIDList>)|(skus|sku <SKUIDList>)|allskus|gsuite]
gam create building <Name> <BuildingAttributes>*
gam update building <BuildIngID> <BuildingAttributes>*
gam create building <Name> <BuildingAttribute>*
gam update building <BuildIngID> <BuildingAttribute>*
gam delete building <BuildingID>
gam info building <BuildingID>
gam print buildings [todrive]
@ -1216,8 +1231,8 @@ gam update feature <Name> name <Name>
gam delete feature <Name>
gam print features [todrive]
gam create resource <ResourceID> <Name> <ResourceAttributes>*
gam update resource <ResourceID> <ResourceAttributes>*
gam create resource <ResourceID> <Name> <ResourceAttribute>*
gam update resource <ResourceID> <ResourceAttribute>*
gam delete resource <ResourceID>
gam info resource <ResourceID>
gam print resources [todrive] [allfields] <ResourceFieldName>* [query <String>]
@ -1229,8 +1244,8 @@ gam info schema <SchemaName>
gam show schema|schemas
gam print schema|schemas
gam create user <EmailAddress> <UserAttributes>*
gam update user <UserItem> <UserAttributes>* [clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>]
gam create user <EmailAddress> <UserAttribute>*
gam update user <UserItem> <UserAttribute>* [clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>]
gam delete user <UserItem>
gam undelete user <UserItem> [org|ou <OrgUnitPath>]
gam info user [<UserItem>] [noaliases] [nogroups] [nolicenses|nolicences] [noschemas] [schemas|custom <SchemaNameList>] [userview] [skus|sku <SKUIDList>]
@ -1251,8 +1266,8 @@ gam create verify|verification <DomainName>
gam update verify|verification <DomainName> cname|txt|text|site|file
gam info verify|verification
gam create course [id|alias <CourseAlias>] <CourseAttributes>*
gam update course <CourseID> <CourseAttributes>+
gam create course [id|alias <CourseAlias>] <CourseAttribute>*
gam update course <CourseID> <CourseAttribute>+
gam delete course <CourseID>
gam info course <CourseID>
gam print courses [todrive] [teacher <UserItem>] [student <UserItem>] [states <CourseStateList>]
@ -1272,32 +1287,6 @@ gam show guardian|guardians [invitedguardian <EmailAddress>] [student <StudentIt
gam print guardian|guardians [todrive] [invitedguardian <EmailAddress>] [student <StudentItem>] [invitations [states <GuardianStateList>]] [<UserTypeEntity>]
gam cancel guardianinvitation|guardianinvitations <GuardianInvitationID> <StudentItem>
gam update printer <PrinterID> <PrinterAttributes>+
gam delete printer <PrinterID>
gam info printer <PrinterID> [everything]
gam print printers [todrive] [(query <QueryPrinter>)|(queries <QueryPrinterList>)] [type <String>] [status <String>] [extrafields <String>]
gam printer <PrinterID> add user|manager|owner <EmailAddress>|[domain:]<DomainName>|public [notify]
gam printer <PrinterID> delete <EmailAddress>|[domain:]<DomainName>|public
gam printer <PrinterID> showacl
gam printjob <PrintJobID> cancel
gam printjob <PrintJobID> delete
gam printjob <PrintJobID> resubmit <PrinterID>
gam printjob <PrinterID>|any fetch
[olderthan|newerthan <PrintJobAge>] [query <QueryPrintJob>]
[status <PrintJobStatus>]
[orderby <PrintJobOrderByFieldName> [ascending|descending]]
[owner|user <EmailAddress>]
[limit <Number>] [drivedir|(targetfolder <FilePath>)]
gam printjob <PrinterID> submit <FileName>|<URL> [name|title <String>] (tag <String>)*
gam print printjobs [todrive] [printer|printerid <PrinterID>]
[olderthan|newerthan <PrintJobAge>] [query <QueryPrintJob>]
[status <PrintJobStatus>]
[orderby <PrintJobOrderByFieldName> [ascending|descending]]
[owner|user <EmailAddress>]
[limit <Number>]
gam create vaultexport|export matter <MatterItem> [name <name>] corpus <drive|mail|groups|hangouts_chat>
(accounts <EmailAddressList>) | (orgunit|ou <OrgUnitPath>) | (teamdrives <TeamDriveList>) | (rooms <ChatRoomList>) | everyone
[scope <all_data|held_data|unprocessed_data>]
@ -1340,8 +1329,8 @@ gam <UserTypeEntity> update backupcodes|backupcode|verificationcodes
gam <UserTypeEntity> delete|del backupcodes|backupcode|verificationcodes
gam <UserTypeEntity> show backupcodes|backupcode|verificationcodes
gam <UserTypeEntity> add calendar <CalendarItem> <CalendarAttributes>*
gam <UserTypeEntity> update calendar <CalendarItem>|primary <CalendarAttributes>+
gam <UserTypeEntity> add calendar <CalendarItem> <CalendarAttribute>*
gam <UserTypeEntity> update calendar <CalendarItem>|primary <CalendarAttribute>+
gam <UserTypeEntity> delete|del calendar <CalendarItem>
gam <UserTypeEntity> show calendars
gam <UserTypeEntity> info calendar <CalendarItem>|primary
@ -1360,8 +1349,8 @@ gam <UserTypeEntity> show fileinfo <DriveFileID> [allfields|<DriveFieldName>*]
gam <UserTypeEntity> show filerevisions <DriveFileID>
gam <UserTypeEntity> show filetree [anyowner] (orderby <DriveOrderByFieldName> [ascending|descending])*
gam <UserTypeEntity> create|add drivefile [drivefilename <DriveFileName>] <DriveFileAddAttributes>* [csv] [todrive] [returnidonly]
gam <UserTypeEntity> update drivefile (id <DriveFileID)|(drivefilename <DriveFileName>)|(query <QueryDriveFile) [copy] [newfilename <DriveFileName>] <DriveFileUpdateAttributes>*
gam <UserTypeEntity> create|add drivefile [drivefilename <DriveFileName>] <DriveFileAddAttribute>* [csv] [todrive] [returnidonly]
gam <UserTypeEntity> update drivefile (id <DriveFileID)|(drivefilename <DriveFileName>)|(query <QueryDriveFile) [copy] [newfilename <DriveFileName>] <DriveFileUpdateAttribute>*
gam <UserTypeEntity> get drivefile (id <DriveFileID>)|(drivefilename <DriveFileName>)|(query <QueryDriveFile>)
[revision <Number>] [(format <FileFormatList>)|(csvsheet <String>)]
[targetfolder <FilePath>] [targetname -|<FileName>] [overwrite] [showprogress]
@ -1398,7 +1387,7 @@ gam <UserTypeEntity> show tokens|token [clientid <ClientID>]
gam <UserTypeEntity> print tokens|token [todrive] [clientid <ClientID>]
gam print tokens|token [todrive] [clientid <ClientID>] [<UserTypeEntity>]
gam <UserTypeEntity> update user <UserAttributes>
gam <UserTypeEntity> update user <UserAttribute>
gam <UserTypeEntity> deprovision|deprov

View File

@ -1093,7 +1093,7 @@ def buildAlertCenterGAPIObject(user):
def buildActivityGAPIObject(user):
userEmail = convertUIDtoEmailAddress(user)
return (userEmail, buildGAPIServiceObject('appsactivity', userEmail))
return (userEmail, buildGAPIServiceObject('driveactivity', userEmail))
def buildDriveGAPIObject(user):
@ -2720,7 +2720,7 @@ def deletePhoto(users):
for user in users:
i += 1
print(f'Deleting photo for {user}{currentCount(i, count)}')
gapi.call(cd.users().photos(), 'delete', userKey=user)
gapi.call(cd.users().photos(), 'delete', userKey=user, soft_errors=True)
def printDriveSettings(users):
@ -2788,23 +2788,58 @@ def getTeamDriveThemes(users):
def printDriveActivity(users):
drive_ancestorId = 'root'
drive_fileId = None
def _get_user_info(user_id):
if user_id.startswith('people/'):
user_id = user_id[7:]
entry = user_info.get(user_id)
if entry is None:
result = gapi.call(cd.users(), 'get',
soft_errors=True,
userKey=user_id, fields='primaryEmail,name.fullName')
if result:
entry = (result['primaryEmail'], result['name']['fullName'])
else:
entry = (f'uid:{user_id}', 'Unknown')
user_info[user_id] = entry
return entry
def _update_known_users(structure):
if isinstance(structure, list):
for v in structure:
if isinstance(v, (dict, list)):
_update_known_users(v)
elif isinstance(structure, dict):
for k, v in sorted(iter(structure.items())):
if k != 'knownUser':
if isinstance(v, (dict, list)):
_update_known_users(v)
else:
entry = _get_user_info(v['personName'])
v['emailAddress'] = entry[0]
v['personName'] = entry[1]
break
cd = buildGAPIObject('directory')
drive_key = 'ancestorName'
drive_fileId = 'root'
user_info = {}
todrive = False
titles = [
'user.name', 'user.permissionId', 'target.id', 'target.name',
'target.mimeType'
'user.name', 'user.emailAddress', 'target.id', 'target.name',
'target.mimeType', 'eventTime'
]
sort_titles = titles[:]
csvRows = []
i = 5
while i < len(sys.argv):
activity_object = sys.argv[i].lower().replace('_', '')
if activity_object == 'fileid':
drive_fileId = sys.argv[i + 1]
drive_ancestorId = None
drive_key = 'itemName'
i += 2
elif activity_object == 'folderid':
drive_ancestorId = sys.argv[i + 1]
drive_fileId = sys.argv[i + 1]
drive_key = 'ancestorName'
i += 2
elif activity_object == 'todrive':
todrive = True
@ -2812,23 +2847,57 @@ def printDriveActivity(users):
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam <users> show driveactivity')
for user in users:
user, activity = buildActivityGAPIObject(user)
if not activity:
continue
page_token = None
total_items = 0
kwargs = {drive_key: f'items/{drive_fileId}',
'pageToken': page_token}
page_message = gapi.got_total_items_msg(f'Activities for {user}', '')
feed = gapi.get_all_pages(activity.activities(),
'list',
'activities',
page_message=page_message,
source='drive.google.com',
userId='me',
drive_ancestorId=drive_ancestorId,
groupingStrategy='none',
drive_fileId=drive_fileId)
for item in feed:
while True:
feed = gapi.call(activity.activity(), 'query', body=kwargs)
page_token, total_items = gapi.process_page(feed, 'activities', None, total_items, page_message, None)
kwargs['pageToken'] = page_token
if feed:
for activity_event in feed.get('activities', []):
event_row = {}
actors = activity_event.get('actors', [])
if actors:
userId = actors[0].get('user', {}).get('knownUser', {}).get('personName', '')
if not userId:
userId = actors[0].get('impersonation', {}).get('impersonatedUser', {}).get('knownUser', {}).get('personName', '')
if userId:
entry = _get_user_info(userId)
event_row['user.name'] = entry[1]
event_row['user.emailAddress'] = entry[0]
targets = activity_event.get('targets', [])
if targets:
driveItem = targets[0].get('driveItem')
if driveItem:
event_row['target.id'] = driveItem['name'][6:]
event_row['target.name'] = driveItem['title']
event_row['target.mimeType'] = driveItem['mimeType']
else:
teamDrive = targets[0].get('teamDrive')
if teamDrive:
event_row['target.id'] = teamDrive['name'][11:]
event_row['target.name'] = teamDrive['title']
if 'timestamp' in activity_event:
event_row['eventTime'] = activity_event.pop('timestamp')
elif 'timeRange' in activity_event:
timeRange = activity_event.pop('timeRange')
event_row['eventTime'] = f'{timeRange["startTime"]}-{timeRange["endTime"]}'
_update_known_users(activity_event)
display.add_row_titles_to_csv_file(
utils.flatten_json(item['combinedEvent']), csvRows, titles)
utils.flatten_json(activity_event, flattened=event_row), csvRows, titles)
del feed
if not page_token:
gapi.finalize_page_message(page_message)
break
display.sort_csv_titles(sort_titles, titles)
display.write_csv_file(csvRows, titles, 'Drive Activity', todrive)
@ -3570,6 +3639,10 @@ def getDriveFileAttribute(i, body, parameters, myarg, update=False):
return i
def has_multiple_parents(body):
return len(body.get('parents', [])) > 1
def doUpdateDriveFile(users):
fileIdSelection = {'fileIds': [], 'query': None}
media_body = None
@ -3618,6 +3691,9 @@ def doUpdateDriveFile(users):
body.setdefault('parents', [])
for a_parent in more_parents:
body['parents'].append({'id': a_parent})
if has_multiple_parents(body):
sys.stderr.write(f"Multiple parents ({len(body['parents'])}) specified for {user}, only one is allowed.\n")
continue
if fileIdSelection['query']:
fileIdSelection['fileIds'] = doDriveSearch(
drive, query=fileIdSelection['query'])
@ -3705,6 +3781,9 @@ def createDriveFile(users):
body.setdefault('parents', [])
for a_parent in more_parents:
body['parents'].append({'id': a_parent})
if has_multiple_parents(body):
sys.stderr.write(f"Multiple parents ({len(body['parents'])}) specified for {user}, only one is allowed.\n")
continue
if parameters[DFA_LOCALFILEPATH]:
media_body = googleapiclient.http.MediaFileUpload(
parameters[DFA_LOCALFILEPATH],

View File

@ -218,6 +218,61 @@ def got_total_items_first_last_msg(items):
return f'Got {TOTAL_ITEMS_MARKER} {items}: {FIRST_ITEM_MARKER} - {LAST_ITEM_MARKER}' + '\n'
def process_page(page, items, all_items, total_items, page_message, message_attribute):
"""Process one page of a Google service function response.
Append a list of items to the aggregate list of items
Args:
page: list of items
items: see get_all_pages
all_items: aggregate list of items
total_items: length of all_items
page_message: see get_all_pages
message_attribute: get_all_pages
Returns:
The page token and total number of items
"""
if page:
page_token = page.get('nextPageToken')
page_items = page.get(items, [])
num_page_items = len(page_items)
total_items += num_page_items
if all_items is not None:
all_items.extend(page_items)
else:
page_token = None
num_page_items = 0
# Show a paging message to the user that indicates paging progress
if page_message:
show_message = page_message.replace(TOTAL_ITEMS_MARKER,
str(total_items))
if message_attribute:
first_item = page_items[0] if num_page_items > 0 else {}
last_item = page_items[-1] if num_page_items > 1 else first_item
if isinstance(message_attribute, str):
first_item = str(first_item.get(message_attribute, ''))
last_item = str(last_item.get(message_attribute, ''))
else:
for attr in message_attribute:
first_item = first_item.get(attr, {})
last_item = last_item.get(attr, {})
first_item = str(first_item)
last_item = str(last_item)
show_message = show_message.replace(FIRST_ITEM_MARKER, first_item)
show_message = show_message.replace(LAST_ITEM_MARKER, last_item)
sys.stderr.write('\r')
sys.stderr.flush()
sys.stderr.write(show_message)
return (page_token, total_items)
def finalize_page_message(page_message):
""" Issue final page_message """
if page_message and (page_message[-1] != '\n'):
sys.stderr.write('\r\n')
sys.stderr.flush()
def get_all_pages(service,
function,
items='items',
@ -274,46 +329,12 @@ def get_all_pages(service,
soft_errors=soft_errors,
throw_reasons=throw_reasons,
retry_reasons=retry_reasons,
pageToken=page_token,
**kwargs)
if page:
page_token = page.get('nextPageToken')
page_items = page.get(items, [])
num_page_items = len(page_items)
total_items += num_page_items
all_items.extend(page_items)
else:
page_token = None
num_page_items = 0
# Show a paging message to the user that indicates paging progress
if page_message:
show_message = page_message.replace(TOTAL_ITEMS_MARKER,
str(total_items))
if message_attribute:
first_item = page_items[0] if num_page_items > 0 else {}
last_item = page_items[-1] if num_page_items > 1 else first_item
if type(message_attribute) is str:
first_item = str(first_item.get(message_attribute, ''))
last_item = str(last_item.get(message_attribute, ''))
else:
for attr in message_attribute:
first_item = first_item.get(attr, {})
last_item = last_item.get(attr, {})
first_item = str(first_item)
last_item = str(last_item)
show_message = show_message.replace(FIRST_ITEM_MARKER, first_item)
show_message = show_message.replace(LAST_ITEM_MARKER, last_item)
sys.stderr.write('\r')
sys.stderr.flush()
sys.stderr.write(show_message)
page_token, total_items = process_page(page, items, all_items, total_items, page_message, message_attribute)
if not page_token:
# End the paging status message and return all items.
if page_message and (page_message[-1] != '\n'):
sys.stderr.write('\r\n')
sys.stderr.flush()
finalize_page_message(page_message)
return all_items
kwargs['pageToken'] = page_token
# TODO: Make this private once all execution related items that use this method

View File

@ -1,3 +1,7 @@
import sys
import googleapiclient
import gam
from gam.var import *
from gam import controlflow
@ -160,6 +164,7 @@ def print_():
members = membersCountOnly = managers = managersCountOnly = owners = ownersCountOnly = False
gapi_directory_customer.setTrueCustomerId()
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
usemember = None
memberDelimiter = '\n'
todrive = False
titles = []
@ -171,6 +176,10 @@ def print_():
if myarg == 'todrive':
todrive = True
i += 1
elif myarg == 'enterprisemember':
member = gam.convertUIDtoEmailAddress(sys.argv[i + 1], email_types=['user', 'group'])
usemember = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels"
i += 2
elif myarg == 'delimiter':
memberDelimiter = sys.argv[i + 1]
i += 2
@ -222,8 +231,28 @@ def print_():
display.add_titles_to_csv_file([
'Owners',
], titles)
gam.printGettingAllItems('Groups', None)
gam.printGettingAllItems('Groups', usemember)
page_message = gapi.got_total_items_first_last_msg('Groups')
if usemember:
try:
result = gapi.get_all_pages(ci.groups().memberships(),
'searchTransitiveGroups',
'memberships',
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O],
page_message=page_message,
message_attribute=['groupKey', 'id'],
parent='groups/-', query=usemember,
fields='nextPageToken,memberships(group,groupKey(id),relationType)',
pageSize=1000)
except googleapiclient.errors.HttpError:
controlflow.system_error_exit(
2,
f'enterprisemember requires Enterprise license')
entityList = []
for entity in result:
if entity['relationType'] == 'DIRECT':
entityList.append(gapi.call(ci.groups(), 'get', name=entity['group']))
else:
entityList = gapi.get_all_pages(ci.groups(),
'list',
'groups',
@ -319,6 +348,7 @@ def print_members():
todrive = False
gapi_directory_customer.setTrueCustomerId()
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
usemember = None
roles = []
titles = ['group']
csvRows = []
@ -339,6 +369,10 @@ def print_members():
f'{role} is not a valid role for "gam print group-members {myarg}"'
)
i += 2
elif myarg == 'enterprisemember':
member = gam.convertUIDtoEmailAddress(sys.argv[i + 1], email_types=['user', 'group'])
usemember = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels"
i += 2
elif myarg in ['cigroup', 'cigroups']:
group_email = gam.normalizeEmailAddressOrUID(sys.argv[i + 1])
groups_to_get = [group_email]
@ -347,8 +381,25 @@ def print_members():
controlflow.invalid_argument_exit(sys.argv[i],
'gam print cigroup-members')
if not groups_to_get:
gam.printGettingAllItems('Groups', None)
gam.printGettingAllItems('Groups', usemember)
page_message = gapi.got_total_items_first_last_msg('Groups')
if usemember:
try:
groups_to_get = gapi.get_all_pages(ci.groups().memberships(),
'searchTransitiveGroups',
'memberships',
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O],
message_attribute=['groupKey', 'id'],
page_message=page_message,
parent='groups/-', query=usemember,
pageSize=1000,
fields='nextPageToken,memberships(groupKey(id),relationType)')
except googleapiclient.errors.HttpError:
controlflow.system_error_exit(
2,
f'enterprisemember requires Enterprise license')
groups_to_get = [group['groupKey']['id'] for group in groups_to_get if group['relationType'] == 'DIRECT']
else:
groups_to_get = gapi.get_all_pages(
ci.groups(),
'list',

View File

@ -16,11 +16,22 @@ from gam.gapi.directory import orgunits as gapi_directory_orgunits
from gam import utils
def _display_cros_command_result(cd, device_id, command_id, times_to_check_status):
print(f'deviceId: {device_id}, commandId: {command_id}')
final_states = {'EXPIRED', 'CANCELLED', 'EXECUTED_BY_CLIENT'}
for _ in range(0, times_to_check_status):
time.sleep(2)
result = gapi.call(cd.customer().devices().chromeos().commands(), 'get',
customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id,
commandId=command_id)
display.print_json(result)
if result.get('state') in final_states:
return
def issue_command():
cd = gapi_directory.build()
i, devices = getCrOSDeviceEntity(3, cd)
body = {}
final_states = ['EXPIRED', 'CANCELLED', 'EXECUTED_BY_CLIENT']
valid_commands = gapi.get_enum_values_minus_unspecified(
cd._rootDesc['schemas']
['DirectoryChromeosdevicesIssueCommandRequest']
@ -68,36 +79,28 @@ def issue_command():
except googleapiclient.errors.HttpError:
controlflow.system_error_exit(4, '400 response from Google. This ' \
'usually indicates the devices was not in a state where it will' \
' accept the command. For example, reboot and take_a_screenshot' \
' accept the command. For example, reboot, set_volume and take_a_screenshot' \
' require the device to be in auto-start kiosk app mode.')
display.print_json(result)
command_id = result.get('commandId')
for i in range(0, times_to_check_status):
time.sleep(2)
result = gapi.call(cd.customer().devices().chromeos().commands(), 'get',
customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id,
commandId=command_id)
display.print_json(result)
state = result.get('state')
if state in final_states:
break
_display_cros_command_result(cd, device_id, command_id, times_to_check_status)
def get_command():
cd = gapi_directory.build()
i, devices = getCrOSDeviceEntity(3, cd)
command_id = None
times_to_check_status = 1
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'commandid':
command_id = sys.argv[i+1]
i += 2
elif myarg == 'timestocheckstatus':
times_to_check_status = int(sys.argv[i+1])
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam getcommand cros')
for device_id in devices:
result = gapi.call(cd.customer().devices().chromeos().commands(), 'get',
customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id,
commandId=command_id)
display.print_json(result)
_display_cros_command_result(cd, device_id, command_id, times_to_check_status)
def doUpdateCros():
cd = gapi_directory.build()

View File

@ -11,6 +11,7 @@ from gam import display
from gam import fileutils
from gam import gapi
from gam.gapi import storage as gapi_storage
from gam.gapi.directory import orgunits as gapi_directory_orgunits
from gam import utils
@ -131,7 +132,7 @@ def createExport():
i += 2
elif searchMethod == 'ORG_UNIT':
body['query']['orgUnitInfo'] = {
'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
'orgUnitId': gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])[1]
}
i += 2
elif searchMethod == 'SHARED_DRIVE':
@ -301,7 +302,7 @@ def createHold():
i += 2
elif myarg in ['orgunit', 'ou']:
body['orgUnit'] = {
'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
'orgUnitId': gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])[1]
}
i += 2
elif myarg in ['start', 'starttime']:
@ -407,7 +408,7 @@ def getHoldInfo():
acct_email = gam.convertUIDtoEmailAddress(uid, cd, [account_type])
results['accounts'][i]['email'] = acct_email
if 'orgUnit' in results:
results['orgUnit']['orgUnitPath'] = gam.doGetOrgInfo(
results['orgUnit']['orgUnitPath'] = gapi_directory_orgunits.info(
results['orgUnit']['orgUnitId'], return_attrib='orgUnitPath')
display.print_json(results)
@ -496,7 +497,7 @@ def updateHold():
i += 2
elif myarg in ['orgunit', 'ou']:
body['orgUnit'] = {
'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
'orgUnitId': gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])[1]
}
i += 2
elif myarg in ['start', 'starttime']:

View File

@ -233,7 +233,6 @@ PRODUCTID_NAME_MAPPINGS = {
'101034': 'G Suite Archived',
'Google-Apps': 'Google Workspace',
'Google-Chrome-Device-Management': 'Google Chrome Device Management',
'Google-Coordinate': 'Google Coordinate',
'Google-Drive-storage': 'Google Drive Storage',
'Google-Vault': 'Google Vault',
}
@ -241,7 +240,6 @@ PRODUCTID_NAME_MAPPINGS = {
# Legacy APIs that use v1 discovery. Newer APIs should all use v2.
V1_DISCOVERY_APIS = {
'admin',
'appsactivity',
'calendar',
'drive',
'oauth2',
@ -260,7 +258,7 @@ API_NAME_MAPPING = {
API_VER_MAPPING = {
'alertcenter': 'v1beta1',
'appsactivity': 'v1',
'driveactivity': 'v2',
'calendar': 'v3',
'cbcm': 'v1.1beta1',
'classroom': 'v1',
@ -293,8 +291,8 @@ USERINFO_EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
API_SCOPE_MAPPING = {
'alertcenter': ['https://www.googleapis.com/auth/apps.alerts',],
'appsactivity': [
'https://www.googleapis.com/auth/activity',
'driveactivity': [
'https://www.googleapis.com/auth/drive.activity',
'https://www.googleapis.com/auth/drive',
],
'calendar': ['https://www.googleapis.com/auth/calendar',],
@ -907,6 +905,7 @@ CROS_ARGUMENT_TO_PROPERTY_MAP = {
'ethernetmacaddress0': ['ethernetMacAddress0',],
'firmwareversion': ['firmwareVersion',],
'lastenrollmenttime': ['lastEnrollmentTime',],
'lastknownnetwork': ['lastKnownNetwork'],
'lastsync': ['lastSync',],
'location': ['annotatedLocation',],
'macaddress': ['macAddress',],