Compare commits

..

42 Commits
v4.87 ... v4.89

Author SHA1 Message Date
Jay Lee
fa52d9e89e Merge branch 'master' of https://github.com/jay0lee/GAM 2019-07-11 09:55:41 -04:00
Jay Lee
6383aa594a Batch Drive Deletes 2019-07-11 09:55:20 -04:00
Ross Scroggs
54eb59c27b Convert team to shared in vault export; prepare for the future (#971)
* Convert team to shared in vault export

Restore _getValidCourseStates(croom), it is used by _getCourseStates in print courses/course-participants

* Update GamCommands.txt

* Fix typo

* Update gam.py
2019-07-10 15:35:06 -04:00
Ross Scroggs
3877f8309b Update GamCommands.txt (#969) 2019-07-10 12:17:53 -04:00
Jay Lee
d8b0681831 Merge branch 'master' of https://github.com/jay0lee/GAM 2019-07-10 12:17:13 -04:00
Jay Lee
cd8303dbea GAM 4.89 2019-07-10 12:17:00 -04:00
Ross Scroggs
ab51d6e931 Update GamCommands.txt (#968) 2019-07-10 11:33:49 -04:00
Jay Lee
b7e402dca2 confidential vault, generalize enums from discovery, query RCs 2019-07-10 09:33:21 -04:00
Jay Lee
842040a8b3 run e2e tests again 2019-07-09 21:39:27 -04:00
Jay Lee
1205da5d34 Update linux-x86_64-before-install.sh 2019-07-09 20:36:33 -04:00
Jay Lee
f40824fedd disable e2e so Python 3.7.4 compile finishes 2019-07-09 13:18:29 -04:00
Jay Lee
67fa0cbc61 } 2019-07-09 12:23:29 -04:00
Jay Lee
e029c77f76 Python 3.7.4, accept newer Python/OpenSSL versions 2019-07-09 11:09:09 -04:00
Jay Lee
2b23ae4e67 remove dnspython requirement, minor fixes 2019-07-02 12:21:51 -04:00
Jay Lee
c8ecc23c9c Remove dnspython in favor of simple Google DNS JSON API 2019-07-02 11:13:31 -04:00
Ross Scroggs
94f8959879 Handle group members with no status (#962)
* Handle group members with no status

* Omit Advanced  form

* Update gam.py
2019-07-01 12:02:08 -04:00
Jay Lee
2cdb8eb44d fix message header argument, make sure we remove all headers in cases of duplicate header or non-matching case 2019-06-27 14:39:03 -04:00
Ross Scroggs
ebc1d1ecb3 Clean up sendOrDropEmail (#961)
* Clean up sendOrDropEmail

* Date allowed in all commands, only sets kwargs for import/insert

* Two updates

Allow headers in draft/import/insert/send email
Quote arguments in todrive decscription

* Make requested changes

* Don't user sendser as an alias for from as they can be different things in SMTP

* On import message, default to not checking for spam
2019-06-27 09:55:00 -04:00
Jay Lee
d9e99334d2 new options and improvements to message send/drop/draft 2019-06-25 13:24:36 -04:00
Jay Lee
a06776bbbd Update var.py 2019-06-24 10:12:32 -04:00
Jay Lee
aecd725a71 Update .travis.yml 2019-06-21 21:11:59 -04:00
Jay Lee
ad8e9364e1 Update .travis.yml 2019-06-21 20:49:33 -04:00
Ross Scroggs
b812fef1c3 Update draft/import/insert/sendemail (#959)
* Update draft/import/insert/sendemail

If possible, use same option names as already exist in my Gam for same commands.

Include charset with file.

* Code cleanup
2019-06-21 20:22:44 -04:00
Ross Scroggs
56371214b0 Document gam <UserTypeEntity> sendemail (#958) 2019-06-20 18:32:28 -04:00
Jay Lee
3669a86f41 use gam.py, send to admin 2019-06-20 16:40:27 -04:00
Jay Lee
4095bf63ef Email send/import/insert/draft 2019-06-20 16:29:46 -04:00
Jay Lee
d75321ca8a put on the breaks :-) 2019-06-20 11:52:07 -04:00
Jay Lee
c931e1cdd7 retry on version extended also 2019-06-20 11:43:45 -04:00
Jay Lee
ea8cda72c7 full email on send 2019-06-19 16:46:14 -04:00
Jay Lee
f0bcd7888a don't use runCmdforUsers() 2019-06-19 15:53:48 -04:00
Jay Lee
6a08a66221 fi 2019-06-19 15:45:58 -04:00
Jay Lee
5e58edf598 command to send (spartan) test messages 2019-06-19 15:41:01 -04:00
Jay Lee
1e79772ec1 catch/retry DNS errors if we need to refresh token on startup 2019-06-19 15:09:58 -04:00
Jay Lee
3461ce053f cleanup print jobs 2019-06-19 12:49:50 -04:00
Jay Lee
9c84ce30e8 retry on ServerNotFound DNS issues 2019-06-19 12:15:23 -04:00
Jay Lee
93ca63e133 Merge branch 'master' of https://github.com/jay0lee/GAM 2019-06-18 14:54:49 -04:00
Jay Lee
e367c86fce fix issue with over-escaping aue model 2019-06-18 14:54:33 -04:00
Ross Scroggs
34dc12994a Three updates (#956)
Add notregex to GAM_CSV_ROW_FILTER to allow selection rows that don't have a particular value

Standarize formatting timestamps

Display mobile.patchSecurityLevel as a date/time
2019-06-18 14:03:19 -04:00
Jay Lee
df4de5ce4b move CrOS AUE dates to dynamic file 2019-06-18 11:20:24 -04:00
Jay Lee
ea630480b2 create cros-aue-dates.json 2019-06-18 11:14:43 -04:00
Jay Lee
db1159cd0d remove custom code verifier patch and force google-auth-oauthlib==0.4.0.
Next release may break code_verifier auto-gen based on
https://github.com/googleapis/google-auth-library-python-oauthlib/pull/48 so we lock at 0.4.0 for now.
2019-06-18 10:23:50 -04:00
Ross Scroggs
ccf1dc0585 Update calendar ACL commands (#953)
* Update calendar ACL commands

Your change was not backwards compatible;  sys.argv[4] was previously ignored

* Code cleanup
2019-06-17 19:52:08 -04:00
10 changed files with 684 additions and 495 deletions

View File

@@ -2,7 +2,7 @@ if: tag IS blank
env:
global:
- BUILD_PYTHON_VERSION=3.7.3
- BUILD_PYTHON_VERSION=3.7.4
- BUILD_OPENSSL_VERSION=1.1.1c
- PATCHELF_VERSION=0.9
- MUSL_VERSION=1.1.22
@@ -134,10 +134,12 @@ install:
script:
- $gam version extended
- $gam version | grep travis # travis should be part of the path (not /tmp or such)
- if [ "$VMTYPE" == "build" ]; then $gam version | grep "Python ${BUILD_PYTHON_VERSION//./\\.}"; fi # We should be building with latest Python
- if [ "$VMTYPE" == "build" ]; then $gam version extended | grep "OpenSSL ${BUILD_OPENSSL_VERSION//./\\.}"; fi # We should be using OpenSSL 1.1.1+
# determine which Python version GAM is built with and ensure it's at least build version from above.
- if [ "VMTYPE" == "build" ]; then vline=$(gamd version | grep "Python "); python_line=($vline); this_python=${python_line[1]}; tools/a_atleast_b.py $this_python $BUILD_PYTHON_VERSION; fi
# determine which OpenSSL version GAM is built with and ensure it's at least build version from above.
- if [ "VMTYPE" == "build" ]; then vline=$(gamd version extended | grep "OpenSSL "); openssl_line=($vline); this_openssl=${openssl_line[1]}; tools/a_atleast_b.py $this_openssl $BUILD_OPENSSL_VERSION; fi
- if [ "$VMTYPE" == "build" ]; then $gam version extended | grep TLSv1\.[23]; fi # Builds should default TLS 1.2 or 1.3 to Google
- if [ "$VMTYPE" == "build" ]; then GAM_TLS_MIN_VERSION=TLSv1_2 $gam version extended location tls-v1-0.badssl.com:1010; [[ $? == 3 ]]; fi # expect fail if server doesn't support our TLS version
- if [ "$VMTYPE" == "build" ]; then GAM_TLS_MIN_VERSION=TLSv1_2 $gam version extended location tls-v1-0.badssl.com:1010; [[ $? == 3 ]]; fi # expect fail since server doesn't support our TLS version
- export jid="$(cut -d'.' -f2 <<<"$TRAVIS_JOB_NUMBER")"
- if [ "$TRAVIS_EVENT_TYPE" != "pull_request" ]; then export e2e=true; fi
- if [ "$e2e" = true ]; then export gam_user=gam-travis-$jid@pdl.jaylee.us; fi
@@ -160,12 +162,14 @@ script:
do echo $newbase-bulkuser-$i >> sample.csv;
done; fi
- if [ "$e2e" = true ]; then $gam create user $newuser firstname Travis lastname $jid password random travis.jid $jid; fi
- if [ "$e2e" = true ]; then $gam user $gam_user sendemail recipient $newuser subject "test message $newbase" message "Travis test message"; fi
- if [ "$e2e" = true ]; then $gam create group $newgroup name "Travis $jid group" description "This is a description" isarchived true; fi
- if [ "$e2e" = true ]; then $gam user $newuser add license gsuitebusiness; fi
- if [ "$e2e" = true ]; then $gam update group $newgroup add owner $gam_user; fi
- if [ "$e2e" = true ]; then $gam update group $newgroup add member $newuser; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam create user ~~email~~ firstname "Travis Bulk" lastname ~~email~~ travis.jid $jid; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user ~email add license gsuitebusiness; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user $gam_user sendemail recipient ~~email~~@pdl.jaylee.us subject "test message $newbase" message "Travis test message"; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update group $newgroup add member ~email; fi
- if [ "$e2e" = true ]; then $gam info group $newgroup; fi
- if [ "$e2e" = true ]; then $gam user $gam_user check serviceaccount; fi
@@ -173,7 +177,11 @@ script:
- if [ "$e2e" = true ]; then $gam user $newuser show imap; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user $newuser delegate to ~email; fi
- if [ "$e2e" = true ]; then $gam user $newuser show delegates; fi
- if [ "$e2e" = true ]; then $gam users "$newbase-bulkuser-01 $newbase-bulkuser-02 $newbase-bulkuser-03" delete messages query in:anywhere maxtodelete 99999 doit; fi
- if [ "$e2e" = true ]; then $gam user $gam_user importemail subject "Travis import $newbase" message "This is a test import" labels IMPORTANT,UNREAD,INBOX,STARRED; fi
- if [ "$e2e" = true ]; then $gam user $gam_user insertemail subject "Travis insert $newbase" file gam.py labels INBOX,UNREAD; fi # yep body is gam code
- if [ "$e2e" = true ]; then $gam user $gam_user sendemail subject "Travis send $gam_user $newbase" file gam.py recipient admin@pdl.jaylee.us; fi
- if [ "$e2e" = true ]; then $gam user $gam_user draftemail subject "Travis draft $newbase" message "Draft message test"; fi
- if [ "$e2e" = true ]; then $gam users "$gam_user $newbase-bulkuser-01 $newbase-bulkuser-02 $newbase-bulkuser-03" delete messages query in:anywhere maxtodelete 99999 doit; fi
- if [ "$e2e" = true ]; then $gam users "$newbase-bulkuser-04 $newbase-bulkuser-05 $newbase-bulkuser-06" trash messages query in:anywhere maxtotrash 99999 doit; fi
- if [ "$e2e" = true ]; then $gam users "$newbase-bulkuser-07 $newbase-bulkuser-08 $newbase-bulkuser-09" modify messages query in:anywhere maxtomodify 99999 addlabel IMPORTANT addlabel STARRED doit; fi
- if [ "$e2e" = true ]; then $gam create feature name Whiteboard-$newbase; fi
@@ -197,10 +205,8 @@ script:
- if [ "$e2e" = true ]; then $gam csv printers.csv gam info printer ~id; fi
- if [ "$e2e" = true ]; then $gam print printjobs; fi
- if [ "$e2e" = true ]; then $gam csv printers.csv gam printjob ~id fetch; fi
- if [ "$e2e" = true ]; then $gam print printjobs | $gam csv - gam printjob ~id delete; fi
- if [ "$e2e" = true ]; then $gam csv printers.csv gam delete printer ~id; fi
#- if [ "$e2e" = true ]; then $gam calendar id:$newresource add editor $newuser; fi
#- if [ "$e2e" = true ]; then $gam calendar id:$newresource update read domain; fi
#- if [ "$e2e" = true ]; then $gam calendar id:$newresource showacl; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user ~email add calendar id:$newresource; fi
- if [ "$e2e" = true ]; then $gam delete resource $newresource; fi
- if [ "$e2e" = true ]; then $gam delete feature Whiteboard-$newbase; fi

View File

@@ -143,6 +143,7 @@ If an item contains spaces, it should be surrounded by ".
<ASPID> ::= <String>
<BuildingID> ::= <String>|id:<String>
<CalendarACLRole> ::= editor|freebusy|freebusyreader|owner|reader|writer
<CalendarACLRuleID> ::= user:<EmailAddress>|group:<EmailAddress>|domain:<DomainName>|default
<CalendarColorIndex> ::= <Number in range 1-24>
<CalendarItem> ::= <EmailAddress>|<String>
<ChatRoom> ::= <String>
@@ -798,7 +799,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
<UserBasicAttribute>|
<UserMultiAttribute>
gam version [check] [simple]
gam version [check|checkrc|simple|extended] [location <HostName>]
gam help
gam batch <FileName>|- [charset <Charset>]
@@ -939,15 +940,17 @@ gam print aliases|nicknames [todrive] [shownoneditable] [nogroups] [nousers] [(q
gam calendar <CalendarItem> add <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain [<DomainName>])|default [sendnotifications <Boolean>]
gam calendar <CalendarItem> update <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain [<DomainName>])|default [sendnotifications <Boolean>]
gam calendar <CalendarItem> del|delete <CalendarACLRole> <EmailAddress>|(domain [<DomainName>])|default
gam calendar <CalendarItem> del|delete id <CalendarACLRuleID>
gam calendar <CalendarItem> showacl
gam calendar <CalendarItem> printacl [todrive]
<EventNotificationAttribute> ::=
notifyattendees|(sendnotifications <Boolean>)|(sendupdates all|enternalonly|none)
notifyattendees|(sendnotifications <Boolean>)|(sendupdates all|enternalonly|none)
The following attributes are equivalent:
notifyattendees - sendupdates all
sendnotifications false - sendupdates none
sendnotifications true - sendupdates all
notifyattendees - sendupdates all
sendnotifications false - sendupdates none
sendnotifications true - sendupdates all
<EventAttributes> ::=
anyonecanaddself|
@@ -973,15 +976,15 @@ The following attributes are equivalent:
(visibility default|public|prvate)
<EventSelectProperty:> ::=
(after <Time>)|
(before <Time>)|
includeeleted|
includehidden|
(query <QueryCalendar>)|
(updatedmin <Time>)
(after <Time>)|
(before <Time>)|
includeeleted|
includehidden|
(query <QueryCalendar>)|
(updatedmin <Time>)
<EventDisplayProperty> ::=
(timezone <TimeZone>)
(timezone <TimeZone>)
gam calendar <CalendarItem> addevent <EventAttributes>+ [<EventNotificationAttribute>]
gam calendar <CalendarItem> deleteevent id|eventid <EventID> [doit] [<EventNotificationAttribute>]
@@ -1089,7 +1092,7 @@ gam create resource <ResourceID> <Name> <ResourceAttributes>*
gam update resource <ResourceID> <ResourceAttributes>*
gam delete resource <ResourceID>
gam info resource <ResourceID>
gam print resources [todrive] [allfields] <ResourceFieldName>*
gam print resources [todrive] [allfields] <ResourceFieldName>* [query <String>]
gam create schema|schemas <SchemaName> <SchemaFieldDefinition>+
gam update schema <SchemaName> <SchemaFieldDefinition>* (deletefield <FieldName>)*
@@ -1170,9 +1173,11 @@ gam print printjobs [todrive] [printer|printerid <PrinterID>]
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>]
[terms <terms>] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] [timezone <TimeZone>] [format mbox|pst]
[excludedrafts <Boolean>] [driveversiondate <Date>|<Time>] [includeteamdrives] [includerooms]
[includeaccessinfo <Boolean>]
[terms <terms>] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] [timezone <TimeZone>]
[excludedrafts <Boolean>] [format mbox|pst] [showconfidentialmodecontent <Boolean>]
[includerooms]
[driveversiondate <Date>|<Time>] [includeshareddrives|includeteamdrives] [includeaccessinfo <Boolean>]
[region any|europe|us]
gam delete export <MatterItem> <ExportItem>
gam info export <MatterItem> <ExportItem>
gam print exports [todrive] [matters <MatterItemList>]
@@ -1283,6 +1288,21 @@ gam <UserTypeEntity> untrash messages query <QueryGmail> [doit] [max_to_untrash|
gam <UserTypeEntity> show gmailprofile [todrive]
gam <UserTypeEntity> draftemail [recipient|to <EmailAddress>] [from <EmailAddress>]
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
gam <UserTypeEntity> importemail [recipient|to <EmailAddress>] [from <EmailAddress>]
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
[labels <LabelNameList>] (header <String> <String>)*
[deleted] [date <Time>]
[nevercheckspam] [processforcalendar]
gam <UserTypeEntity> insertemail [recipient|to <EmailAddress>] [from <EmailAddress>]
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
[labels <LabelNameList>] (header <String> <String>)*
[deleted] [date <Time>]
gam <UserTypeEntity> sendemail [recipient|to <EmailAddress>] [from <EmailAddress>]
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
(header <String> <String>)*
gam <UserTypeEntity> create|add delegate|delegates <EmailAddress>
gam <UserTypeEntity> delegate|delegates to <EmailAddress>
gam <UserTypeEntity> delete|del delegate|delegates <EmailAddress>

256
src/cros-aue-dates.json Normal file
View File

@@ -0,0 +1,256 @@
{
"acer ac700": "2016-08-01T00:00:00.000Z",
"acer c7 chromebook": "2017-10-01T00:00:00.000Z",
"acer c7 chromebook (c710)": "2017-10-01T00:00:00.000Z",
"acer c720 chromebook": "2019-06-01T00:00:00.000Z",
"acer c740 chromebook": "2019-06-01T00:00:00.000Z",
"acer chromebase": "2020-08-01T00:00:00.000Z",
"acer chromebase 24": "2021-06-01T00:00:00.000Z",
"acer chromebook 11 (c720, c720p)": "2019-06-01T00:00:00.000Z",
"acer chromebook 11 (c732, c732t, c732l, c732lt)": "2023-11-01T00:00:00.000Z",
"acer chromebook 11 (c740)": "2020-06-01T00:00:00.000Z",
"acer chromebook 11 (c771, c771t)": "2022-11-01T00:00:00.000Z",
"acer chromebook 11 (cb3-111, c730, c730e)": "2019-08-01T00:00:00.000Z",
"acer chromebook 11 (cb3-131, c735)": "2021-01-01T00:00:00.000Z",
"acer chromebook 11 (cb311-8h, cb311-8ht)": "2023-11-01T00:00:00.000Z",
"acer chromebook 11 n7 (c731, c731t)": "2022-01-01T00:00:00.000Z",
"acer chromebook 13 (cb5-311)": "2019-09-01T00:00:00.000Z",
"acer chromebook 13 (cb713-1w)": "2024-06-01T00:00:00.000Z",
"acer chromebook 13(cb5-311, c810)": "2019-09-01T00:00:00.000Z",
"acer chromebook 14 (cb3-431)": "2021-06-01T00:00:00.000Z",
"acer chromebook 14 for work (cp5-471)": "2022-11-01T00:00:00.000Z",
"acer chromebook 15 (c910 / cb5-571)": "2020-06-01T00:00:00.000Z",
"acer chromebook 15 (cb3-531)": "2020-06-01T00:00:00.000Z",
"acer chromebook 15 (cb3-532)": "2021-08-01T00:00:00.000Z",
"acer chromebook 15 (cb315-1h,cb315-1ht)": "2023-11-01T00:00:00.000Z",
"acer chromebook 15 (cb5-571, c910)": "2020-06-01T00:00:00.000Z",
"acer chromebook 15 (cb515-1h,cb515-1ht)": "2023-11-01T00:00:00.000Z",
"acer chromebook 311": "2025-06-01T00:00:00.000Z",
"acer chromebook 311 (c721, c733, c733u, c733t)": "2025-06-01T00:00:00.000Z",
"acer chromebook 315": "2025-06-01T00:00:00.000Z",
"acer chromebook 315 (cb315-2h)": "2025-06-01T00:00:00.000Z",
"acer chromebook 512 (c851, c851t)": "2025-06-01T00:00:00.000Z",
"acer chromebook 514": "2023-11-01T00:00:00.000Z",
"acer chromebook 714 (cb714-1w / cb714-1wt)": "2024-06-01T00:00:00.000Z",
"acer chromebook 715 (cb715-1w / cb715-1wt)": "2024-06-01T00:00:00.000Z",
"acer chromebook r11 (cb5-132t, c738t)": "2021-06-01T00:00:00.000Z",
"acer chromebook r13 (cb5-312t)": "2021-09-01T00:00:00.000Z",
"acer chromebook spin 11 (cp311-h1, cp311-1hn)": "2023-11-01T00:00:00.000Z",
"acer chromebook spin 11 (r751t)": "2023-11-01T00:00:00.000Z",
"acer chromebook spin 13 (cp713-1wn)": "2024-06-01T00:00:00.000Z",
"acer chromebook spin 15 (cp315)": "2023-11-01T00:00:00.000Z",
"acer chromebook spin 311 (r721t)": "2025-06-01T00:00:00.000Z",
"acer chromebook spin 511": "2025-06-01T00:00:00.000Z",
"acer chromebook spin 511 (r752t, r752tn)": "2025-06-01T00:00:00.000Z",
"acer chromebook spin 512 (r851tn)": "2025-06-01T00:00:00.000Z",
"acer chromebook tab 10": "2023-08-01T00:00:00.000Z",
"acer chromebox": "2019-09-01T00:00:00.000Z",
"acer chromebox cxi2": "2020-06-01T00:00:00.000Z",
"acer chromebox cxi2 / cxv2": "2020-06-01T00:00:00.000Z",
"acer chromebox cxi3": "2024-06-01T00:00:00.000Z",
"aopen chromebase commercial": "2020-09-01T00:00:00.000Z",
"aopen chromebase mini": "2022-02-01T00:00:00.000Z",
"aopen chromebox commercial": "2020-09-01T00:00:00.000Z",
"aopen chromebox commercial 2": "2024-06-01T00:00:00.000Z",
"aopen chromebox mini": "2022-02-01T00:00:00.000Z",
"asi chromebook": "2020-06-01T00:00:00.000Z",
"asus chromebit cs10": "2020-11-01T00:00:00.000Z",
"asus chromebook c200": "2019-06-01T00:00:00.000Z",
"asus chromebook c200ma": "2019-06-01T00:00:00.000Z",
"asus chromebook c201pa": "2020-06-01T00:00:00.000Z",
"asus chromebook c202sa": "2021-06-01T00:00:00.000Z",
"asus chromebook c204": "2025-06-01T00:00:00.000Z",
"asus chromebook c213na": "2023-11-01T00:00:00.000Z",
"asus chromebook c223": "2023-11-01T00:00:00.000Z",
"asus chromebook c300": "2019-08-01T00:00:00.000Z",
"asus chromebook c300ma": "2019-08-01T00:00:00.000Z",
"asus chromebook c300sa / c301sa": "2021-06-01T00:00:00.000Z",
"asus chromebook c403": "2023-11-01T00:00:00.000Z",
"asus chromebook c423": "2023-11-01T00:00:00.000Z",
"asus chromebook c523": "2023-11-01T00:00:00.000Z",
"asus chromebook flip c100pa": "2020-07-01T00:00:00.000Z",
"asus chromebook flip c101pa": "2023-08-01T00:00:00.000Z",
"asus chromebook flip c213": "2023-11-01T00:00:00.000Z",
"asus chromebook flip c214": "2025-06-01T00:00:00.000Z",
"asus chromebook flip c302": "2022-11-01T00:00:00.000Z",
"asus chromebook flip c434": "2024-06-01T00:00:00.000Z",
"asus chromebook tablet ct100": "2023-08-01T00:00:00.000Z",
"asus chromebox (cn60)": "2019-09-01T00:00:00.000Z",
"asus chromebox 2 (cn62)": "2021-06-01T00:00:00.000Z",
"asus chromebox 3": "2024-06-01T00:00:00.000Z",
"asus chromebox 3 (cn65)": "2024-06-01T00:00:00.000Z",
"asus chromebox cn60": "2019-09-01T00:00:00.000Z",
"asus chromebox cn62": "2021-06-01T00:00:00.000Z",
"bobicus chromebook 11": "2020-06-01T00:00:00.000Z",
"chromebook 11 (c730 / cb3-111)": "2019-08-01T00:00:00.000Z",
"chromebook 11 (c735)": "2021-01-01T00:00:00.000Z",
"chromebook 15 (cb515 - 1ht / 1h)": "2023-11-01T00:00:00.000Z",
"chromebook 311 (c721)": "2025-06-01T00:00:00.000Z",
"chromebook pcm-116e": "2020-06-01T00:00:00.000Z",
"consumer chromebook": "2020-06-01T00:00:00.000Z",
"cr-48": "2015-12-01T00:00:00.000Z",
"crambo chromebook": "2020-06-01T00:00:00.000Z",
"ctl chromebook j41 / j41t": "2023-11-01T00:00:00.000Z",
"ctl chromebook nl7": "2023-11-01T00:00:00.000Z",
"ctl chromebook nl7t-360 / nl7tw-360": "2023-11-01T00:00:00.000Z",
"ctl chromebook tab tx1": "2023-08-01T00:00:00.000Z",
"ctl chromebook tablet tx1 for education": "2023-08-01T00:00:00.000Z",
"ctl chromebox cbx1": "2024-06-01T00:00:00.000Z",
"ctl j2 / j4 chromebook": "2020-06-01T00:00:00.000Z",
"ctl j5 chromebook": "2021-08-01T00:00:00.000Z",
"ctl n6 education chromebook": "2020-06-01T00:00:00.000Z",
"ctl nl61 chromebook": "2021-08-01T00:00:00.000Z",
"dell chromebook 11": "2019-06-01T00:00:00.000Z",
"dell chromebook 11 (3120)": "2020-06-01T00:00:00.000Z",
"dell chromebook 11 (3180)": "2022-05-01T00:00:00.000Z",
"dell chromebook 11 (5190)": "2023-11-01T00:00:00.000Z",
"dell chromebook 11 2-in-1 (3189)": "2022-05-01T00:00:00.000Z",
"dell chromebook 11 2-in-1 (5190)": "2023-11-01T00:00:00.000Z",
"dell chromebook 13 (3380)": "2022-11-01T00:00:00.000Z",
"dell chromebook 13 (7310)": "2020-09-01T00:00:00.000Z",
"dell chromebook 3100": "2025-06-01T00:00:00.000Z",
"dell chromebook 3100 2-in-1": "2025-06-01T00:00:00.000Z",
"dell chromebook 3400": "2025-06-01T00:00:00.000Z",
"dell chromebox": "2019-09-01T00:00:00.000Z",
"dell inspiron chromebook 14 2-in-1 (7486)": "2024-06-01T00:00:00.000Z",
"edugear chromebook k": "2020-06-01T00:00:00.000Z",
"edugear chromebook m": "2020-06-01T00:00:00.000Z",
"edugear chromebook r": "2020-06-01T00:00:00.000Z",
"edugear cmt chromebook": "2021-08-01T00:00:00.000Z",
"edxis chromebook": "2020-06-01T00:00:00.000Z",
"edxis education chromebook": "2020-06-01T00:00:00.000Z",
"epik 11.6\" chromebook elb1101": "2020-06-01T00:00:00.000Z",
"google chromebook pixel": "2018-06-01T00:00:00.000Z",
"google chromebook pixel (2015)": "2020-06-01T00:00:00.000Z",
"google cr-48": "2015-12-01T00:00:00.000Z",
"google pixel slate": "2024-06-01T00:00:00.000Z",
"google pixelbook": "2024-06-01T00:00:00.000Z",
"haier chromebook 11": "2020-06-01T00:00:00.000Z",
"haier chromebook 11 c": "2021-08-01T00:00:00.000Z",
"haier chromebook 11 g2": "2020-09-01T00:00:00.000Z",
"haier chromebook 11e": "2020-06-01T00:00:00.000Z",
"hexa chromebook pi": "2020-06-01T00:00:00.000Z",
"hisense chromebook 11": "2020-06-01T00:00:00.000Z",
"hp chromebook 11 1100-1199 / hp chromebook 11 g1": "2018-10-01T00:00:00.000Z",
"hp chromebook 11 2000-2099 / hp chromebook 11 g2": "2019-06-01T00:00:00.000Z",
"hp chromebook 11 2100-2199 / hp chromebook 11 g3": "2020-06-01T00:00:00.000Z",
"hp chromebook 11 2200-2299 / hp chromebook 11 g4/g4 ee": "2020-06-01T00:00:00.000Z",
"hp chromebook 11 g1": "2018-10-01T00:00:00.000Z",
"hp chromebook 11 g2": "2019-06-01T00:00:00.000Z",
"hp chromebook 11 g3": "2020-06-01T00:00:00.000Z",
"hp chromebook 11 g4/g4 ee": "2020-06-01T00:00:00.000Z",
"hp chromebook 11 g5": "2021-07-01T00:00:00.000Z",
"hp chromebook 11 g5 / hp chromebook 11-vxxx": "2021-07-01T00:00:00.000Z",
"hp chromebook 11 g5 ee": "2022-01-01T00:00:00.000Z",
"hp chromebook 11 g6 ee": "2023-11-01T00:00:00.000Z",
"hp chromebook 11 g7 ee": "2025-06-01T00:00:00.000Z",
"hp chromebook 11a g6 ee": "2025-06-01T00:00:00.000Z",
"hp chromebook 13 g1": "2022-11-01T00:00:00.000Z",
"hp chromebook 14": "2019-06-01T00:00:00.000Z",
"hp chromebook 14 / hp chromebook 14 g5": "2023-11-01T00:00:00.000Z",
"hp chromebook 14 ak000-099 / hp chromebook 14 g4": "2021-09-01T00:00:00.000Z",
"hp chromebook 14 db0000-db0999": "2025-06-01T00:00:00.000Z",
"hp chromebook 14 g3": "2019-10-01T00:00:00.000Z",
"hp chromebook 14 g4": "2021-09-01T00:00:00.000Z",
"hp chromebook 14 g5": "2023-11-01T00:00:00.000Z",
"hp chromebook 14 x000-x999 / hp chromebook 14 g3": "2019-10-01T00:00:00.000Z",
"hp chromebook 14a g5": "2025-06-01T00:00:00.000Z",
"hp chromebook 15 g1": "2024-06-01T00:00:00.000Z",
"hp chromebook x2 ": "2024-06-01T00:00:00.000Z",
"hp chromebook x360 11 g1 ee": "2023-11-01T00:00:00.000Z",
"hp chromebook x360 11 g2 ee": "2025-06-01T00:00:00.000Z",
"hp chromebook x360 14": "2024-06-01T00:00:00.000Z",
"hp chromebook x360 14 g1": "2024-06-01T00:00:00.000Z",
"hp chromebox cb1-(000-099) / hp chromebox g1/ hp chromebox for meetings": "2019-09-01T00:00:00.000Z",
"hp chromebox g1": "2019-09-01T00:00:00.000Z",
"hp chromebox g2": "2024-06-01T00:00:00.000Z",
"hp pavilion chromebook 14": "2018-02-01T00:00:00.000Z",
"jp sa couto chromebook": "2020-06-01T00:00:00.000Z",
"lava xolo chromebook": "2020-06-01T00:00:00.000Z",
"lenovo 100e chromebook": "2023-11-01T00:00:00.000Z",
"lenovo 100e chromebook 2nd gen": "2025-06-01T00:00:00.000Z",
"lenovo 100e chromebook 2nd gen mtk": "2025-06-01T00:00:00.000Z",
"lenovo 100s chromebook": "2020-09-01T00:00:00.000Z",
"lenovo 14e chromebook": "2025-06-01T00:00:00.000Z",
"lenovo 300e chromebook": "2025-06-01T00:00:00.000Z",
"lenovo 300e chromebook 2nd gen": "2025-06-01T00:00:00.000Z",
"lenovo 300e chromebook 2nd gen mtk": "2025-06-01T00:00:00.000Z",
"lenovo 500e chromebook": "2023-11-01T00:00:00.000Z",
"lenovo 500e chromebook 2nd gen": "2025-06-01T00:00:00.000Z",
"lenovo chromebook c330": "2022-06-01T00:00:00.000Z",
"lenovo chromebook s330": "2022-06-01T00:00:00.000Z",
"lenovo flex 11 chromebook": "2022-06-01T00:00:00.000Z",
"lenovo ideapad c330 chromebook": "2022-06-01T00:00:00.000Z",
"lenovo ideapad s330 chromebook": "2022-06-01T00:00:00.000Z",
"lenovo n20 chromebook": "2019-06-01T00:00:00.000Z",
"lenovo n21 chromebook": "2020-06-01T00:00:00.000Z",
"lenovo n22 chromebook": "2021-06-01T00:00:00.000Z",
"lenovo n23 chromebook": "2021-06-01T00:00:00.000Z",
"lenovo n23 yoga chromebook": "2022-06-01T00:00:00.000Z",
"lenovo n42 chromebook": "2021-06-01T00:00:00.000Z",
"lenovo thinkcentre chromebox": "2020-06-01T00:00:00.000Z",
"lenovo thinkpad 11e 3rd gen chromebook": "2021-06-01T00:00:00.000Z",
"lenovo thinkpad 11e 4th gen chromebook": "2023-11-01T00:00:00.000Z",
"lenovo thinkpad 11e chromebook": "2019-06-01T00:00:00.000Z",
"lenovo thinkpad 11e chromebook (4th gen)/lenovo thinkpad yoga 11e chromebook (4th gen)": "2023-11-01T00:00:00.000Z",
"lenovo thinkpad 13": "2022-11-01T00:00:00.000Z",
"lenovo thinkpad x131e chromebook": "2018-06-01T00:00:00.000Z",
"lenovo yoga c630 chromebook": "2024-06-01T00:00:00.000Z",
"lg chromebase (22cb25s)": "2020-06-01T00:00:00.000Z",
"lg chromebase (22cv241)": "2019-06-01T00:00:00.000Z",
"lumos education chromebook": "2020-06-01T00:00:00.000Z",
"m&a chromebook": "2020-06-01T00:00:00.000Z",
"mecer chromebook": "2020-06-01T00:00:00.000Z",
"mecer v2 chromebook": "2021-08-01T00:00:00.000Z",
"medion chromebook akoya s2013 ": "2020-06-01T00:00:00.000Z",
"medion chromebook s2015": "2020-06-01T00:00:00.000Z",
"multilaser chromebook m11c": "2021-08-01T00:00:00.000Z",
"ncomputing chromebook cx100": "2020-06-01T00:00:00.000Z",
"ncomputing chromebook cx110": "2020-06-01T00:00:00.000Z",
"nexian chromebook 11.6\"": "2020-06-01T00:00:00.000Z",
"pcmerge chromebook al116": "2023-11-01T00:00:00.000Z",
"pcmerge chromebookpcm-116e/pcm-116eb": "2020-06-01T00:00:00.000Z",
"pcmerge chromebookpcm-116t-432b": "2021-08-01T00:00:00.000Z",
"poin2 chromebook 11": "2020-06-01T00:00:00.000Z",
"poin2 chromebook 11c": "2022-11-01T00:00:00.000Z",
"poin2 chromebook 14": "2022-03-01T00:00:00.000Z",
"positivo chromebook c216b": "2021-08-01T00:00:00.000Z",
"positivo chromebook ch1190": "2020-06-01T00:00:00.000Z",
"promethean chromebox": "2024-06-01T00:00:00.000Z",
"prowise 11.6\" entry line chromebook": "2020-06-01T00:00:00.000Z",
"prowise chromebook eduline": "2023-11-01T00:00:00.000Z",
"prowise chromebook entryline": "2020-06-01T00:00:00.000Z",
"prowise chromebook proline": "2021-08-01T00:00:00.000Z",
"prowise proline chromebook": "2021-08-01T00:00:00.000Z",
"rgs education chromebook": "2020-06-01T00:00:00.000Z",
"samsung chromebook": "2018-07-01T00:00:00.000Z",
"samsung chromebook - xe303": "2018-07-01T00:00:00.000Z",
"samsung chromebook 2 11": "2019-06-01T00:00:00.000Z",
"samsung chromebook 2 11 - xe500c12": "2020-06-01T00:00:00.000Z",
"samsung chromebook 2 13": "2019-06-01T00:00:00.000Z",
"samsung chromebook 3": "2021-06-01T00:00:00.000Z",
"samsung chromebook plus": "2023-08-01T00:00:00.000Z",
"samsung chromebook plus (lte)": "2024-06-01T00:00:00.000Z",
"samsung chromebook plus (v2)": "2024-06-01T00:00:00.000Z",
"samsung chromebook pro": "2022-11-01T00:00:00.000Z",
"samsung chromebook series 5": "2016-06-01T00:00:00.000Z",
"samsung chromebook series 5 550": "2017-05-01T00:00:00.000Z",
"samsung chromebox series 3": "2018-03-01T00:00:00.000Z",
"sector 5 e1 rugged chromebook": "2020-06-01T00:00:00.000Z",
"sector 5 e3 chromebook": "2023-11-01T00:00:00.000Z",
"senkatel c1101 chromebook": "2020-06-01T00:00:00.000Z",
"thinkpad 11e chromebook 3rd gen (yoga/clamshell)": "2021-06-01T00:00:00.000Z",
"thinkpad 13 chromebook": "2022-11-01T00:00:00.000Z",
"toshiba chromebook": "2019-06-01T00:00:00.000Z",
"toshiba chromebook 2": "2020-06-01T00:00:00.000Z",
"toshiba chromebook 2 (2015 edition)": "2020-09-01T00:00:00.000Z",
"true idc chromebook": "2020-06-01T00:00:00.000Z",
"true idc chromebook 11": "2020-06-01T00:00:00.000Z",
"videonet chromebook": "2020-06-01T00:00:00.000Z",
"videonet chromebook bl10": "2020-06-01T00:00:00.000Z",
"viewsonic nmp660 chromebox": "2024-06-01T00:00:00.000Z",
"viglen chromebook 11": "2020-06-01T00:00:00.000Z",
"viglen chromebook 11c": "2023-11-01T00:00:00.000Z",
"viglen chromebook 360": "2021-08-01T00:00:00.000Z",
"xolo chromebook": "2020-06-01T00:00:00.000Z"
}

View File

@@ -27,6 +27,7 @@ import configparser
import csv
import datetime
import difflib
from email import message_from_string
import hashlib
import io
import json
@@ -50,12 +51,10 @@ import uuid
import webbrowser
import zipfile
import http.client as http_client
from email.mime.text import MIMEText
from multiprocessing import Pool
from multiprocessing import freeze_support
from urllib.parse import urlencode, urlparse
from passlib.hash import sha512_crypt
import dns.resolver
import dateutil.parser
import googleapiclient
@@ -113,32 +112,6 @@ def _createHttpObj(cache=None, override_min_tls=None, override_max_tls=None):
return httplib2.Http(tls_maximum_version=tls_maximum_version, tls_minimum_version=tls_minimum_version,
cache=cache)
# Override google_auth_oauthlib classes so we use PKCE
# Remove once https://github.com/googleapis/google-auth-library-python-oauthlib/pull/42
# is landed and released.
def _authorization_url(self, **kwargs):
kwargs.setdefault('access_type', 'offline')
rnd = SystemRandom()
random_verifier = [rnd.choice(URL_SAFE_CHARS) for _ in range(128)]
self.code_verifier = ''.join(random_verifier)
code_hash = hashlib.sha256()
code_hash.update(str.encode(self.code_verifier))
unencoded_challenge = code_hash.digest()
b64_challenge = base64.urlsafe_b64encode(unencoded_challenge)
code_challenge = b64_challenge.decode().split('=')[0]
kwargs.setdefault('code_challenge', code_challenge)
kwargs.setdefault('code_challenge_method', 'S256')
url, state = self.oauth2session.authorization_url(self.client_config['auth_uri'], **kwargs)
return url, state
def _fetch_token(self, **kwargs):
kwargs.setdefault('client_secret', self.client_config['client_secret'])
kwargs.setdefault('code_verifier', self.code_verifier)
return self.oauth2session.fetch_token(self.client_config['token_uri'], **kwargs)
google_auth_oauthlib.flow.Flow.authorization_url = _authorization_url
google_auth_oauthlib.flow.Flow.fetch_token = _fetch_token
def showUsage():
doGAMVersion(checkForArgs=False)
print('''
@@ -630,7 +603,7 @@ def SetGlobalVariables():
ROW_FILTER_COMP_PATTERN = re.compile(r'^(date|time|count)\s*([<>]=?|=|!=)\s*(.+)$', re.IGNORECASE)
ROW_FILTER_BOOL_PATTERN = re.compile(r'^(boolean):(.+)$', re.IGNORECASE)
ROW_FILTER_RE_PATTERN = re.compile(r'^(regex):(.+)$', re.IGNORECASE)
ROW_FILTER_RE_PATTERN = re.compile(r'^(regex|notregex):(.+)$', re.IGNORECASE)
def _getCfgRowFilter(itemName):
value = GC_Defaults[itemName]
@@ -673,7 +646,7 @@ def SetGlobalVariables():
continue
except re.error as e:
systemErrorExit(3, 'Item: {0}, Value: "{1}": "{2}", Invalid RE: {3}'.format(itemName, column, filterStr, e))
systemErrorExit(3, 'Item: {0}, Value: "{1}": {2}, Expected: (date|time|count<Operator><Value>) or (boolean:true|false) or (regex:<RegularExpression>)'.format(itemName, column, filterStr))
systemErrorExit(3, 'Item: {0}, Value: "{1}": {2}, Expected: (date|time|count<Operator><Value>) or (boolean:true|false) or (regex|notregex:<RegularExpression>)'.format(itemName, column, filterStr))
return rowFilters
except (TypeError, ValueError) as e:
systemErrorExit(3, 'Item: {0}, Value: "{1}", Failed to parse as JSON: {2}'.format(itemName, value, str(e)))
@@ -848,10 +821,17 @@ def _getServerTLSUsed(location):
conn = 'https:%s' % netloc
httpc = _createHttpObj()
headers = {'user-agent': GAM_INFO}
try:
httpc.request(url, headers=headers)
except (httplib2.ServerNotFoundError, RuntimeError) as e:
systemErrorExit(4, e)
retries = 5
for n in range(1, retries+1):
try:
httpc.request(url, headers=headers)
break
except (httplib2.ServerNotFoundError, RuntimeError) as e:
if n != retries:
httpc.connections = {}
waitOnFailure(n, retries, str(e))
continue
systemErrorExit(4, str(e))
cipher_name, tls_ver, _ = httpc.connections[conn].sock.cipher()
return tls_ver, cipher_name
@@ -1092,7 +1072,13 @@ def callGAPI(service, function,
service._http.cache = None
continue
systemErrorExit(4, str(e))
except (TypeError, httplib2.ServerNotFoundError, RuntimeError) as e:
except (httplib2.ServerNotFoundError, RuntimeError) as e:
if n != retries:
service._http.connections = {}
waitOnFailure(n, retries, str(e))
continue
systemErrorExit(4, str(e))
except TypeError as e:
systemErrorExit(4, str(e))
def callGAPIpages(service, function, items='items',
@@ -1252,11 +1238,19 @@ def getValidOauth2TxtCredentials(force_refresh=False):
"""Gets OAuth2 credentials which are guaranteed to be fresh and valid."""
credentials = getOauth2TxtStorageCredentials()
if (credentials and credentials.expired) or force_refresh:
try:
credentials.refresh(google_auth_httplib2.Request(_createHttpObj()))
writeCredentials(credentials)
except google.auth.exceptions.RefreshError as e:
systemErrorExit(18, str(e))
retries = 3
for n in range(1, retries+1):
try:
credentials.refresh(google_auth_httplib2.Request(_createHttpObj()))
writeCredentials(credentials)
break
except google.auth.exceptions.RefreshError as e:
systemErrorExit(18, str(e))
except (google.auth.exceptions.TransportError, httplib2.ServerNotFoundError, RuntimeError) as e:
if n != retries:
waitOnFailure(n, retries, str(e))
continue
systemErrorExit(4, str(e))
elif credentials is None or not credentials.valid:
doRequestOAuth()
credentials = getOauth2TxtStorageCredentials()
@@ -1283,6 +1277,10 @@ def getService(api, http):
http.cache = None
return service
except (httplib2.ServerNotFoundError, RuntimeError) as e:
if n != retries:
http.connections = {}
waitOnFailure(n, retries, str(e))
continue
systemErrorExit(4, str(e))
except (googleapiclient.errors.InvalidJsonError, KeyError, ValueError) as e:
http.cache = None
@@ -1410,17 +1408,24 @@ def buildGAPIServiceObject(api, act_as, showAuthError=True):
GM_Globals[GM_CURRENT_API_SCOPES] = API_SCOPE_MAPPING[api]
credentials = getSvcAcctCredentials(GM_Globals[GM_CURRENT_API_SCOPES], act_as)
request = google_auth_httplib2.Request(http)
try:
credentials.refresh(request)
service._http = google_auth_httplib2.AuthorizedHttp(credentials, http=http)
except (httplib2.ServerNotFoundError, RuntimeError) as e:
systemErrorExit(4, e)
except google.auth.exceptions.RefreshError as e:
if isinstance(e.args, tuple):
e = e.args[0]
if showAuthError:
stderrErrorMsg('User {0}: {1}'.format(GM_Globals[GM_CURRENT_API_USER], str(e)))
return handleOAuthTokenError(str(e), True)
retries = 3
for n in range(1, retries+1):
try:
credentials.refresh(request)
service._http = google_auth_httplib2.AuthorizedHttp(credentials, http=http)
break
except (httplib2.ServerNotFoundError, RuntimeError) as e:
if n != retries:
http.connections = {}
waitOnFailure(n, retries, str(e))
continue
systemErrorExit(4, e)
except google.auth.exceptions.RefreshError as e:
if isinstance(e.args, tuple):
e = e.args[0]
if showAuthError:
stderrErrorMsg('User {0}: {1}'.format(GM_Globals[GM_CURRENT_API_USER], str(e)))
return handleOAuthTokenError(str(e), True)
return service
def buildAlertCenterGAPIObject(user):
@@ -1969,9 +1974,6 @@ def doDelCourse():
callGAPI(croom.courses(), 'delete', id=courseId)
print('Deleted Course %s' % courseId)
def _getValidCourseStates(croom):
return [state for state in croom._rootDesc['schemas']['Course']['properties']['courseState']['enum'] if state != 'COURSE_STATE_UNSPECIFIED']
def _getValidatedState(state, validStates):
state = state.upper()
if state not in validStates:
@@ -1992,13 +1994,13 @@ def getCourseAttribute(myarg, value, body, croom, function):
elif myarg in ['owner', 'ownerid', 'teacher']:
body['ownerId'] = normalizeEmailAddressOrUID(value)
elif myarg in ['state', 'status']:
validStates = _getValidCourseStates(croom)
validStates = _getEnumValuesMinusUnspecified(croom._rootDesc['schemas']['Course']['properties']['courseState']['enum'])
body['courseState'] = _getValidatedState(value, validStates)
else:
systemErrorExit(2, '%s is not a valid argument to "gam %s course"' % (myarg, function))
def _getCourseStates(croom, value, courseStates):
validStates = _getValidCourseStates(croom)
validStates = _getEnumValuesMinusUnspecified(croom._rootDesc['schemas']['Course']['properties']['courseState']['enum'])
for state in value.replace(',', ' ').split():
courseStates.append(_getValidatedState(state, validStates))
@@ -2053,11 +2055,11 @@ def doGetDomainInfo():
domainName = sys.argv[3]
result = callGAPI(cd.domains(), 'get', customer=GC_Values[GC_CUSTOMER_ID], domainName=domainName)
if 'creationTime' in result:
result['creationTime'] = str(datetime.datetime.fromtimestamp(int(result['creationTime'])/1000))
result['creationTime'] = utils.formatTimestampYMDHMSF(result['creationTime'])
if 'domainAliases' in result:
for i in range(0, len(result['domainAliases'])):
if 'creationTime' in result['domainAliases'][i]:
result['domainAliases'][i]['creationTime'] = str(datetime.datetime.fromtimestamp(int(result['domainAliases'][i]['creationTime'])/1000))
result['domainAliases'][i]['creationTime'] = utils.formatTimestampYMDHMSF(result['domainAliases'][i]['creationTime'])
print_json(None, result)
def doGetDomainAliasInfo():
@@ -2065,7 +2067,7 @@ def doGetDomainAliasInfo():
alias = sys.argv[3]
result = callGAPI(cd.domainAliases(), 'get', customer=GC_Values[GC_CUSTOMER_ID], domainAliasName=alias)
if 'creationTime' in result:
result['creationTime'] = str(datetime.datetime.fromtimestamp(int(result['creationTime'])/1000))
result['creationTime'] = utils.formatTimestampYMDHMSF(result['creationTime'])
print_json(None, result)
def doGetCustomerInfo():
@@ -2185,7 +2187,7 @@ def doPrintDomains(return_results=False):
if attr in ['kind', 'etag', 'domainAliases', 'isPrimary']:
continue
if attr in ['creationTime',]:
domain[attr] = str(datetime.datetime.fromtimestamp(int(domain[attr])/1000))
domain[attr] = utils.formatTimestampYMDHMSF(domain[attr])
if attr not in titles:
titles.append(attr)
domain_attributes[attr] = domain[attr]
@@ -2200,7 +2202,7 @@ def doPrintDomains(return_results=False):
if attr in ['kind', 'etag']:
continue
if attr in ['creationTime',]:
aliasdomain[attr] = str(datetime.datetime.fromtimestamp(int(aliasdomain[attr])/1000))
aliasdomain[attr] = utils.formatTimestampYMDHMSF(aliasdomain[attr])
if attr not in titles:
titles.append(attr)
aliasdomain_attributes[attr] = aliasdomain[attr]
@@ -2229,7 +2231,7 @@ def doPrintDomainAliases():
if attr in ['kind', 'etag']:
continue
if attr == 'creationTime':
domainAlias[attr] = str(datetime.datetime.fromtimestamp(int(domainAlias[attr])/1000))
domainAlias[attr] = utils.formatTimestampYMDHMSF(domainAlias[attr])
if attr not in titles:
titles.append(attr)
domainAlias_attributes[attr] = domainAlias[attr]
@@ -3074,9 +3076,8 @@ def doPrintPrintJobs():
jobCount = totalJobs
break
continue
updateTime = int(job['updateTime'])/1000
job['createTime'] = datetime.datetime.fromtimestamp(createTime).strftime('%Y-%m-%d %H:%M:%S')
job['updateTime'] = datetime.datetime.fromtimestamp(updateTime).strftime('%Y-%m-%d %H:%M:%S')
job['createTime'] = utils.formatTimestampYMDHMS(job['createTime'])
job['updateTime'] = utils.formatTimestampYMDHMS(job['updateTime'])
job['tags'] = ' '.join(job['tags'])
addRowTitlesToCSVfile(flatten_json(job), csvRows, titles)
if jobCount >= totalJobs:
@@ -3116,12 +3117,9 @@ def doPrintPrinters():
printers = callGAPI(cp.printers(), 'list', q=query, type=printer_type, connection_status=connection_status, extra_fields=extra_fields)
checkCloudPrintResult(printers)
for printer in printers['printers']:
createTime = int(printer['createTime'])/1000
accessTime = int(printer['accessTime'])/1000
updateTime = int(printer['updateTime'])/1000
printer['createTime'] = datetime.datetime.fromtimestamp(createTime).strftime('%Y-%m-%d %H:%M:%S')
printer['accessTime'] = datetime.datetime.fromtimestamp(accessTime).strftime('%Y-%m-%d %H:%M:%S')
printer['updateTime'] = datetime.datetime.fromtimestamp(updateTime).strftime('%Y-%m-%d %H:%M:%S')
printer['createTime'] = utils.formatTimestampYMDHMS(printer['createTime'])
printer['accessTime'] = utils.formatTimestampYMDHMS(printer['accessTime'])
printer['updateTime'] = utils.formatTimestampYMDHMS(printer['updateTime'])
printer['tags'] = ' '.join(printer['tags'])
addRowTitlesToCSVfile(flatten_json(printer), csvRows, titles)
writeCSVfile(csvRows, titles, 'Printers', todrive)
@@ -3544,12 +3542,9 @@ def doGetPrinterInfo():
result = callGAPI(cp.printers(), 'get', printerid=printerid)
checkCloudPrintResult(result)
printer_info = result['printers'][0]
createTime = int(printer_info['createTime'])/1000
accessTime = int(printer_info['accessTime'])/1000
updateTime = int(printer_info['updateTime'])/1000
printer_info['createTime'] = datetime.datetime.fromtimestamp(createTime).strftime('%Y-%m-%d %H:%M:%S')
printer_info['accessTime'] = datetime.datetime.fromtimestamp(accessTime).strftime('%Y-%m-%d %H:%M:%S')
printer_info['updateTime'] = datetime.datetime.fromtimestamp(updateTime).strftime('%Y-%m-%d %H:%M:%S')
printer_info['createTime'] = utils.formatTimestampYMDHMS(printer_info['createTime'])
printer_info['accessTime'] = utils.formatTimestampYMDHMS(printer_info['accessTime'])
printer_info['updateTime'] = utils.formatTimestampYMDHMS(printer_info['updateTime'])
printer_info['tags'] = ' '.join(printer_info['tags'])
if not everything:
del printer_info['capabilities']
@@ -3705,20 +3700,29 @@ def formatACLRule(rule):
return '(Scope: {0}:{1}, Role: {2})'.format(rule['scope']['type'], rule['scope']['value'], rule['role'])
return '(Scope: {0}, Role: {1})'.format(rule['scope']['type'], rule['role'])
def doCalendarShowPrintACL(csvOut=False):
def doCalendarPrintShowACLs(csvFormat):
calendarId, cal = buildCalendarDataGAPIObject(sys.argv[2])
if not cal:
return
toDrive = False
i = 4
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if csvFormat and myarg == 'todrive':
toDrive = True
i += 1
else:
systemErrorExit(2, '%s is not a valid argument for "gam calendar <email> %s"' % (sys.argv[i], ['showacl', 'printacl'][csvFormat]))
acls = callGAPIpages(cal.acl(), 'list', 'items', calendarId=calendarId)
i = 0
if csvOut:
if csvFormat:
titles = []
rows = []
else:
count = len(acls)
for rule in acls:
i += 1
if csvOut:
if csvFormat:
row = flatten_json(rule, None)
for key in row:
if key not in titles:
@@ -3726,8 +3730,8 @@ def doCalendarShowPrintACL(csvOut=False):
rows.append(row)
else:
print('Calendar: {0}, ACL: {1}{2}'.format(calendarId, formatACLRule(rule), currentCount(i, count)))
if csvOut:
writeCSVfile(rows, titles, '%s Calendar ACLs' % calendarId, False)
if csvFormat:
writeCSVfile(rows, titles, '%s Calendar ACLs' % calendarId, toDrive)
def _getCalendarACLScope(i, body):
body['scope'] = {}
@@ -3783,15 +3787,15 @@ def doCalendarDelACL():
calendarId, cal = buildCalendarDataGAPIObject(sys.argv[2])
if not cal:
return
if sys.argv[4].lower() == 'user':
if sys.argv[4].lower() == 'id':
ruleId = sys.argv[5]
print('Removing rights for %s to %s' % (ruleId, calendarId))
callGAPI(cal.acl(), 'delete', calendarId=calendarId, ruleId=ruleId)
else:
body = {'role': 'none'}
_getCalendarACLScope(5, body)
print('Calendar: {0}, {1} ACL: {2}'.format(calendarId, 'Delete', formatACLScope(body)))
callGAPI(cal.acl(), 'insert', calendarId=calendarId, body=body, sendNotifications=False)
elif sys.argv[4].lower() == 'id':
ruleId = sys.argv[5]
print('Removing rights for %s to %s' % (ruleId, calendarId))
callGAPI(cal.acl(), 'delete', calendarId=calendarId, ruleId=ruleId)
def doCalendarWipeData():
calendarId, cal = buildCalendarDataGAPIObject(sys.argv[2])
@@ -4687,11 +4691,25 @@ def deleteDriveFile(users):
file_ids = [fileIds,]
if not file_ids:
print('No files to %s for %s' % (function, user))
i = 0
j = 0
batch_size = 10
del_me_count = len(file_ids)
dbatch = drive.new_batch_http_request(callback=drive_del_result)
method = getattr(drive.files(), function)
for fileId in file_ids:
i += 1
print('%s %s for %s (%s/%s)' % (action, fileId, user, i, len(file_ids)))
callGAPI(drive.files(), function, fileId=fileId, supportsAllDrives=True)
j += 1
dbatch.add(method(fileId=fileId, supportsAllDrives=True))
if len(dbatch._order) == batch_size:
print('%s %s files...' % (action, len(dbatch._order)))
dbatch.execute()
dbatch = drive.new_batch_http_request(callback=drive_del_result)
if len(dbatch._order) > 0:
print('%s %s files...' % (action, len(dbatch._order)))
dbatch.execute()
def drive_del_result(request_id, response, exception):
if exception:
print(exception)
def printDriveFolderContents(feed, folderId, indent):
for f_file in feed:
@@ -5336,6 +5354,58 @@ def transferDriveFiles(users):
if not skipped_files:
break
def sendOrDropEmail(users, method='send'):
body = subject = ''
recipient = labels = sender = None
kwargs = {}
if method in ['insert', 'import']:
kwargs['internalDateSource'] = 'receivedTime'
if method == 'import':
kwargs['neverMarkSpam'] = True
msgHeaders = {}
i = 4
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'message':
body = sys.argv[i+1]
i += 2
elif myarg == 'file':
filename = sys.argv[i+1]
i, encoding = getCharSet(i+2)
body = readFile(filename, encoding=encoding)
elif myarg == 'subject':
subject = sys.argv[i+1]
i += 2
elif myarg in ['recipient', 'to']:
recipient = sys.argv[i+1]
i += 2
elif myarg == 'from':
sender = sys.argv[i+1]
i += 2
elif myarg == 'header':
msgHeaders[sys.argv[i+1]] = sys.argv[i+2]
i += 3
elif method in ['insert', 'import'] and myarg == 'labels':
labels = shlexSplitList(sys.argv[i+1])
i += 2
elif method in ['insert', 'import'] and myarg == 'deleted':
kwargs['deleted'] = True
i += 1
elif myarg == 'date':
msgHeaders['Date'] = getTimeOrDeltaFromNow(sys.argv[i+1])
if method in ['insert', 'import']:
kwargs['internalDateSource'] = 'dateHeader'
i += 2
elif method == 'import' and myarg == 'checkspam':
kwargs['neverMarkSpam'] = False
i += 1
elif method == 'import' and myarg == 'processforcalendar':
kwargs['processForCalendar'] = True
else:
systemErrorExit(2, '%s is not a valid argument for "gam <users> %semail"' % (sys.argv[i], method))
for user in users:
send_email(subject, body, recipient, sender, user, method, labels, msgHeaders, kwargs)
def doImap(users):
enable = getBoolean(sys.argv[4], 'gam <users> imap')
body = {'enabled': enable, 'autoExpunge': True, 'expungeBehavior': 'archive', 'maxFolderSize': 0}
@@ -5782,7 +5852,7 @@ def printShowSmime(users, csvFormat):
result = callGAPI(gmail.users().settings().sendAs().smimeInfo(), 'list', sendAsEmail=sendAsEmail, userId='me')
smimes = result.get('smimeInfo', [])
for j, _ in enumerate(smimes):
smimes[j]['expiration'] = datetime.datetime.fromtimestamp(int(smimes[j]['expiration'])/1000).strftime('%Y-%m-%d %H:%M:%S')
smimes[j]['expiration'] = utils.formatTimestampYMDHMS(smimes[j]['expiration'])
if csvFormat:
for smime in smimes:
addRowTitlesToCSVfile(flatten_json(smime, flattened={'User': user}), csvRows, titles)
@@ -6886,11 +6956,11 @@ def getVacation(users):
print(' Contacts Only: {0}'.format(result['restrictToContacts']))
print(' Domain Only: {0}'.format(result['restrictToDomain']))
if 'startTime' in result:
print(' Start Date: {0}'.format(datetime.datetime.fromtimestamp(int(result['startTime'])/1000).strftime('%Y-%m-%d')))
print(' Start Date: {0}'.format(utils.formatTimestampYMD(result['startTime'])))
else:
print(' Start Date: Started')
if 'endTime' in result:
print(' End Date: {0}'.format(datetime.datetime.fromtimestamp(int(result['endTime'])/1000).strftime('%Y-%m-%d')))
print(' End Date: {0}'.format(utils.formatTimestampYMD(result['endTime'])))
else:
print(' End Date: Not specified')
print(utils.convertUTF8(' Subject: {0}'.format(result.get('responseSubject', 'None'))))
@@ -7573,10 +7643,11 @@ def getCRMService(login_hint):
discoveryServiceUrl=googleapiclient.discovery.V2_DISCOVERY_URI),
http)
def getGAMProjectAPIs():
def getGAMProjectFile(filepath):
file_url = GAM_PROJECT_FILEPATH+filepath
httpObj = _createHttpObj()
_, c = httpObj.request(GAM_PROJECT_APIS, 'GET')
return httpObj, c.decode(UTF8).splitlines()
_, c = httpObj.request(file_url, 'GET')
return c.decode(UTF8)
def enableGAMProjectAPIs(GAMProjectAPIs, httpObj, projectId, checkEnabled, i=0, count=0):
apis = GAMProjectAPIs[:]
@@ -7656,7 +7727,7 @@ def _createClientSecretsOauth2service(httpObj, projectId):
print('Unknown error: %s' % content)
return False
simplehttp, GAMProjectAPIs = getGAMProjectAPIs()
GAMProjectAPIs = getGAMProjectFile('src/project-apis.txt').splitlines()
enableGAMProjectAPIs(GAMProjectAPIs, httpObj, projectId, False)
iam = googleapiclient.discovery.build('iam', 'v1',
http=httpObj, cache_discovery=False,
@@ -7701,6 +7772,7 @@ def _createClientSecretsOauth2service(httpObj, projectId):
client_secret = input('Enter your Client Secret: ').strip()
if not client_secret:
client_secret = input().strip()
simplehttp = _createHttpObj()
client_valid = _checkClientAndSecret(simplehttp, client_id, client_secret)
if client_valid:
break
@@ -7907,7 +7979,7 @@ def doUseProject():
def doUpdateProjects():
_, httpObj, login_hint, projects, _ = _getLoginHintProjects(False)
_, GAMProjectAPIs = getGAMProjectAPIs()
GAMProjectAPIs = getGAMProjectFile('src/project-apis.txt').splitlines()
count = len(projects)
print('User: {0}, Update {1} Projects'.format(login_hint, count))
i = 0
@@ -8159,25 +8231,20 @@ VAULT_SEARCH_METHODS_MAP = {
'ou': 'ORG_UNIT',
'room': 'ROOM',
'rooms': 'ROOM',
'teamdrive': 'TEAM_DRIVE',
'teamdrives': 'TEAM_DRIVE',
'shareddrive': 'SHARED_DRIVE',
'shareddrives': 'SHARED_DRIVE',
'teamdrive': 'SHARED_DRIVE',
'teamdrives': 'SHARED_DRIVE',
}
VAULT_SEARCH_METHODS_LIST = ['accounts', 'orgunit', 'teamdrives', 'rooms', 'everyone']
VAULT_SEARCH_METHODS_LIST = ['accounts', 'orgunit', 'shareddrives', 'rooms', 'everyone']
def doCreateVaultExport():
v = buildGAPIObject('vault')
allowed_corpuses = v._rootDesc['schemas']['Query']['properties']['corpus']['enum']
try:
allowed_corpuses.remove('CORPUS_TYPE_UNSPECIFIED')
except ValueError:
pass
allowed_scopes = v._rootDesc['schemas']['Query']['properties']['dataScope']['enum']
try:
allowed_scopes.remove('DATA_SCOPE_UNSPECIFIED')
except ValueError:
pass
allowed_formats = ['MBOX', 'PST']
allowed_corpuses = _getEnumValuesMinusUnspecified(v._rootDesc['schemas']['Query']['properties']['corpus']['enum'])
allowed_scopes = _getEnumValuesMinusUnspecified(v._rootDesc['schemas']['Query']['properties']['dataScope']['enum'])
allowed_formats = _getEnumValuesMinusUnspecified(v._rootDesc['schemas']['MailExportOptions']['properties']['exportFormat']['enum'])
export_format = 'MBOX'
showConfidentialModeContent = None # default to not even set
matterId = None
body = {'query': {'dataScope': 'ALL_DATA'}, 'exportOptions': {}}
i = 3
@@ -8206,8 +8273,8 @@ def doCreateVaultExport():
elif searchMethod == 'ORG_UNIT':
body['query']['orgUnitInfo'] = {'orgUnitId': getOrgUnitId(sys.argv[i+1])[1]}
i += 2
elif searchMethod == 'TEAM_DRIVE':
body['query']['teamDriveInfo'] = {'teamDriveIds': sys.argv[i+1].split(',')}
elif searchMethod == 'SHARED_DRIVE':
body['query']['sharedDriveInfo'] = {'sharedDriveIds': sys.argv[i+1].split(',')}
i += 2
elif searchMethod == 'ROOM':
body['query']['hangoutsChatInfo'] = {'roomId': sys.argv[i+1].split(',')}
@@ -8237,8 +8304,8 @@ def doCreateVaultExport():
elif myarg in ['driveversiondate']:
body['query'].setdefault('driveOptions', {})['versionDate'] = getDateZeroTimeOrFullTime(sys.argv[i+1])
i += 2
elif myarg in ['includeteamdrives']:
body['query'].setdefault('driveOptions', {})['includeTeamDrives'] = getBoolean(sys.argv[i+1], myarg)
elif myarg in ['includeshareddrives', 'includeteamdrives']:
body['query'].setdefault('driveOptions', {})['includeSharedDrives'] = getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg in ['includerooms']:
body['query']['hangoutsChatOptions'] = {'includeRooms': getBoolean(sys.argv[i+1], myarg)}
@@ -8249,6 +8316,15 @@ def doCreateVaultExport():
print('ERROR: export format can be one of %s, got %s' % (', '.join(allowed_formats), export_format))
sys.exit(3)
i += 2
elif myarg in ['showconfidentialmodecontent']:
showConfidentialModeContent = getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg in ['region']:
allowed_regions = _getEnumValuesMinusUnspecified(v._rootDesc['schemas']['ExportOptions']['properties']['region']['enum'])
body['exportOptions']['region'] = sys.argv[i+1].upper()
if body['exportOptions']['region'] not in allowed_regions:
systemErrorExit(3, 'region should be one of %s, got %s' % (', '.join(allowed_regions), body['exportOptions']['region']))
i += 2
elif myarg in ['includeaccessinfo']:
body['exportOptions'].setdefault('driveOptions', {})['includeAccessInfo'] = getBoolean(sys.argv[i+1], myarg)
i += 2
@@ -8272,6 +8348,8 @@ def doCreateVaultExport():
if options_field:
body['exportOptions'].pop('driveOptions', None)
body['exportOptions'][options_field] = {'exportFormat': export_format}
if showConfidentialModeContent is not None:
body['exportOptions'][options_field]['showConfidentialModeContent'] = showConfidentialModeContent
results = callGAPI(v.matters().exports(), 'create', matterId=matterId, body=body)
print('Created export %s' % results['id'])
print_json(None, results)
@@ -9111,6 +9189,16 @@ def checkGroupExists(cd, group, i=0, count=0):
entityUnknownWarning('Group', group, i, count)
return None
def _checkMemberRoleIsSuspended(member, validRoles, isSuspended):
if validRoles and member.get('role', ROLE_MEMBER) not in validRoles:
return False
if isSuspended is None:
return True
memberStatus = member.get('status', 'UNKNOWN')
if not isSuspended:
return memberStatus != 'SUSPENDED'
return memberStatus == 'SUSPENDED'
UPDATE_GROUP_SUBCMDS = ['add', 'clear', 'delete', 'remove', 'sync', 'update']
GROUP_ROLES_MAP = {
'owner': ROLE_OWNER, 'owners': ROLE_OWNER,
@@ -9315,12 +9403,7 @@ def doUpdateGroup():
if not result:
print('Group already has 0 members')
return
if checkSuspended is None:
users_email = [member.get('email', member['id']) for member in result if not validRoles or member.get('role', ROLE_MEMBER) in validRoles]
elif checkSuspended:
users_email = [member.get('email', member['id']) for member in result if (not validRoles or member.get('role', ROLE_MEMBER) in validRoles) and member['status'] == 'SUSPENDED']
else: # elif not checkSuspended
users_email = [member.get('email', member['id']) for member in result if (not validRoles or member.get('role', ROLE_MEMBER) in validRoles) and member['status'] != 'SUSPENDED']
users_email = [member.get('email', member['id']) for member in result if _checkMemberRoleIsSuspended(member, validRoles, checkSuspended)]
if len(users_email) > 1:
sys.stderr.write('Group: {0}, Will remove {1} {2}{3}s.\n'.format(group, len(users_email), '' if checkSuspended is None else ['Non-suspended ', 'Suspended '][checkSuspended], roles))
for user_email in users_email:
@@ -10340,6 +10423,9 @@ def doGetMobileInfo():
info = callGAPI(cd.mobiledevices(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], resourceId=resourceId)
if 'deviceId' in info:
info['deviceId'] = info['deviceId'].encode('unicode-escape').decode(UTF8)
attrib = 'securityPatchLevel'
if attrib in info and int(info[attrib]):
info[attrib] = utils.formatTimestampYMDHMS(info[attrib])
print_json(None, info)
def print_json(object_name, object_value, spacing=''):
@@ -10428,27 +10514,42 @@ def doSiteVerifyAttempt():
print('ERROR: %s' % str(e))
verify_data = callGAPI(verif.webResource(), 'getToken', body=body)
print('Method: %s' % verify_data['method'])
print('Token: %s' % verify_data['token'])
print('Expected Token: %s' % verify_data['token'])
if verify_data['method'] in ['DNS_CNAME', 'DNS_TXT']:
resolver = dns.resolver.Resolver()
resolver.nameservers = ['8.8.8.8', '8.8.4.4']
simplehttp = _createHttpObj()
base_url = 'https://dns.google/resolve?'
query_params = {}
if verify_data['method'] == 'DNS_CNAME':
cname_token = verify_data['token']
cname_list = cname_token.split(' ')
cname_subdomain = cname_list[0]
try:
answers = resolver.query('%s.%s' % (cname_subdomain, a_domain), 'A')
for answer in answers:
print('DNS Record: %s' % answer)
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
print('ERROR: No such domain found in DNS!')
query_params['name'] = '%s.%s' % (cname_subdomain, a_domain)
query_params['type'] = 'cname'
else:
try:
answers = resolver.query(a_domain, 'TXT')
for answer in answers:
print('DNS Record: %s' % str(answer).replace('"', ''))
except dns.resolver.NXDOMAIN:
print('ERROR: no such domain found in DNS!')
query_params['name'] = a_domain
query_params['type'] = 'txt'
full_url = base_url + urlencode(query_params)
(_, c) = simplehttp.request(full_url, 'GET')
result = json.loads(c.decode(UTF8))
status = result['Status']
if status == 0 and 'Answer' in result:
answers = result['Answer']
if verify_data['method'] == 'DNS_CNAME':
answer = answers[0]['data']
else:
answer = 'no matching record found'
for possible_answer in answers:
possible_answer['data'] = possible_answer['data'].strip('"')
if possible_answer['data'].startswith('google-site-verification'):
answer = possible_answer['data']
break
else:
print('Unrelated TXT record: %s' % possible_answer['data'])
print('Found DNS Record: %s' % answer)
elif status == 0:
systemErrorExit(1, 'DNS record not found')
else:
systemErrorExit(status, DNS_ERROR_CODES_MAP.get(status, 'Unknown error %s' % status))
return
print('SUCCESS!')
print('Verified: %s' % verify_result['site']['identifier'])
@@ -10600,11 +10701,11 @@ def doGetASPs(users):
if asp['creationTime'] == '0':
created_date = 'Unknown'
else:
created_date = datetime.datetime.fromtimestamp(int(asp['creationTime'])/1000).strftime('%Y-%m-%d %H:%M:%S')
created_date = utils.formatTimestampYMDHMS(asp['creationTime'])
if asp['lastTimeUsed'] == '0':
used_date = 'Never'
else:
used_date = datetime.datetime.fromtimestamp(int(asp['lastTimeUsed'])/1000).strftime('%Y-%m-%d %H:%M:%S')
used_date = utils.formatTimestampYMDHMS(asp['lastTimeUsed'])
print(' ID: %s\n Name: %s\n Created: %s\n Last Used: %s\n' % (asp['codeId'], asp['name'], created_date, used_date))
else:
print(' no ASPs for %s\n' % user)
@@ -10887,17 +10988,45 @@ def doDeleteOrg():
print("Deleting organization %s" % name)
callGAPI(cd.orgunits(), 'delete', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=encodeOrgUnitPath(makeOrgUnitPathRelative(name)))
# Send an email
def send_email(msg_subj, msg_txt, msg_rcpt=None):
userId, gmail = buildGmailGAPIObject(_getValueFromOAuth('email'))
if not msg_rcpt:
msg_rcpt = userId
msg = MIMEText(msg_txt)
msg['Subject'] = msg_subj
msg['From'] = userId
msg['To'] = msg_rcpt
callGAPI(gmail.users().messages(), 'send',
userId=userId, body={'raw': base64.urlsafe_b64encode(msg.as_bytes()).decode()})
def send_email(subject, body, recipient=None, sender=None, user=None, method='send', labels=None, msgHeaders={}, kwargs={}):
api_body = {}
default_sender = default_recipient = False
if not user:
user = _getValueFromOAuth('email')
userId, gmail = buildGmailGAPIObject(user)
resource = gmail.users().messages()
if labels:
api_body['labelIds'] = labelsToLabelIds(gmail, labels)
if not sender:
sender = userId
default_sender = True
if not recipient:
recipient = userId
default_recipient = True
msg = message_from_string(body)
for header, value in msgHeaders.items():
msg.__delitem__(header) # can remove multiple case-insensitive matching headers
msg.add_header(header, value)
if subject:
msg.__delitem__('Subject')
msg['Subject'] = subject
if not default_sender:
msg.__delitem__('From')
if not msg['From']:
msg['From'] = sender
if not default_recipient:
msg.__delitem__('to')
if not msg['To']:
msg['To'] = recipient
api_body['raw'] = base64.urlsafe_b64encode(msg.as_bytes()).decode()
if method == 'draft':
resource = gmail.users().drafts()
method = 'create'
api_body = {'message': api_body}
elif method in ['insert', 'import']:
if method == 'import':
method = 'import_'
callGAPI(resource, method, userId=userId, body=api_body, **kwargs)
def addFieldToFieldsList(fieldName, fieldsChoiceMap, fieldsList):
fields = fieldsChoiceMap[fieldName.lower()]
@@ -10959,6 +11088,9 @@ def sortCSVTitles(firstTitle, titles):
for title in restoreTitles[::-1]:
titles.insert(0, title)
def QuotedArgumentList(items):
return ' '.join([item if item and (item.find(' ') == -1) and (item.find(',') == -1) else '"'+item+'"' for item in items])
def writeCSVfile(csvRows, titles, list_type, todrive):
def rowDateTimeFilterMatch(dateMode, rowDate, op, filterDate):
if not rowDate or not isinstance(rowDate, str):
@@ -11018,6 +11150,8 @@ def writeCSVfile(csvRows, titles, list_type, todrive):
continue
if filterVal[0] == 'regex':
csvRows = [row for row in csvRows if filterVal[1].search(str(row.get(column, '')))]
elif filterVal[0] == 'notregex':
csvRows = [row for row in csvRows if not filterVal[1].search(str(row.get(column, '')))]
elif filterVal[0] in ['date', 'time']:
csvRows = [row for row in csvRows if rowDateTimeFilterMatch(filterVal[0] == 'date', row.get(column, ''), filterVal[1], filterVal[2])]
elif filterVal[0] == 'count':
@@ -11061,7 +11195,7 @@ and follow recommend steps to authorize GAM for Drive access.''' % (admin_email)
mimeType = 'text/csv'
else:
mimeType = MIMETYPE_GA_SPREADSHEET
body = {'description': ' '.join(sys.argv),
body = {'description': QuotedArgumentList(sys.argv),
'name': '%s - %s' % (GC_Values[GC_DOMAIN], list_type),
'mimeType': mimeType}
result = callGAPI(drive.files(), 'create', fields='webViewLink',
@@ -11325,12 +11459,12 @@ def doPrintShowAlertFeedback():
for feedbac in feedback:
print(feedbac)
def _getValidAlertFeedbackTypes(ac):
return [aftype for aftype in ac._rootDesc['schemas']['AlertFeedback']['properties']['type']['enum'] if aftype != 'ALERT_FEEDBACK_TYPE_UNSPECIFIED']
def _getEnumValuesMinusUnspecified(values):
return [a_type for a_type in values if '_UNSPECIFIED' not in a_type]
def doCreateAlertFeedback():
_, ac = buildAlertCenterGAPIObject(_getValueFromOAuth('email'))
valid_types = _getValidAlertFeedbackTypes(ac)
valid_types = _getEnumValuesMinusUnspecified(ac._rootDesc['schemas']['AlertFeedback']['properties']['type']['enum'])
alertId = sys.argv[3]
body = {'type': sys.argv[4].upper()}
if body['type'] not in valid_types:
@@ -11809,8 +11943,7 @@ def doPrintGroupMembers():
soft_errors=True,
groupKey=group_email, roles=listRoles, fields=listFields, maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
for member in group_members:
if ((validRoles and member.get('role', ROLE_MEMBER) not in validRoles) or
(checkSuspended is not None and ((not checkSuspended and member['status'] == 'SUSPENDED') or (checkSuspended and member['status'] != 'SUSPENDED')))):
if not _checkMemberRoleIsSuspended(member, validRoles, checkSuspended):
continue
for title in member:
if title not in titles:
@@ -12039,10 +12172,12 @@ def doPrintMobileDevices():
else:
if attrib not in titles:
titles.append(attrib)
if attrib != 'deviceId':
row[attrib] = mobile[attrib]
else:
if attrib == 'deviceId':
row[attrib] = mobile[attrib].encode('unicode-escape').decode(UTF8)
elif attrib == 'securityPatchLevel' and int(mobile[attrib]):
row[attrib] = utils.formatTimestampYMDHMS(mobile[attrib])
else:
row[attrib] = mobile[attrib]
csvRows.append(row)
sortCSVTitles(['resourceId', 'deviceId', 'serialNumber', 'name', 'email', 'status'], titles)
writeCSVfile(csvRows, titles, 'Mobile', todrive)
@@ -12157,12 +12292,14 @@ def _checkTPMVulnerability(cros):
cros['tpmVersionInfo']['tpmVulnerability'] = 'NOT IMPACTED'
def _guessAUE(cros, guessedAUEs):
if not GC_Values.get('CROS_AUE_DATES', None):
GC_Values['CROS_AUE_DATES'] = json.loads(getGAMProjectFile('src/cros-aue-dates.json'))
crosModel = cros.get('model')
if crosModel:
if crosModel not in guessedAUEs:
closest_match = difflib.get_close_matches(crosModel.lower(), CROS_AUE_DATES, n=1)
closest_match = difflib.get_close_matches(crosModel.lower(), GC_Values['CROS_AUE_DATES'], n=1)
if closest_match:
guessedAUEs[crosModel] = {'guessedAUEDate': CROS_AUE_DATES[closest_match[0]],
guessedAUEs[crosModel] = {'guessedAUEDate': GC_Values['CROS_AUE_DATES'][closest_match[0]],
'guessedAUEModel': closest_match[0]}
else:
guessedAUEs[crosModel] = {'guessedAUEDate': u'',
@@ -12608,12 +12745,16 @@ def doPrintResourceCalendars():
fieldsTitles = {}
titles = []
csvRows = []
query = None
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'todrive':
todrive = True
i += 1
elif myarg == 'query':
query = sys.argv[i+1]
i += 2
elif myarg == 'allfields':
fieldsList = []
fieldsTitles = {}
@@ -12636,7 +12777,8 @@ def doPrintResourceCalendars():
page_message = 'Got %%total_items%% Resource Calendars: %%first_item%% - %%last_item%%\n'
resources = callGAPIpages(cd.resources().calendars(), 'list', 'items',
page_message=page_message, message_attribute='resourceId',
customer=GC_Values[GC_CUSTOMER_ID], fields=fields)
customer=GC_Values[GC_CUSTOMER_ID], query=query,
fields=fields)
for resource in resources:
if 'featureInstances' in resource:
resource['featureInstances'] = ','.join([a_feature['feature']['name'] for a_feature in resource.pop('featureInstances')])
@@ -12698,9 +12840,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
groupKey=group, roles=listRoles, fields=listFields, maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
users = []
for member in members:
if (((not groupUserMembersOnly) or (member['type'] == 'USER')) and
(not validRoles or member.get('role', ROLE_MEMBER) in validRoles) and
(checkSuspended is None or (not checkSuspended and member['status'] != 'SUSPENDED') or (checkSuspended and member['status'] == 'SUSPENDED'))):
if ((not groupUserMembersOnly) or (member['type'] == 'USER')) and _checkMemberRoleIsSuspended(member, validRoles, checkSuspended):
users.append(member.get('email', member['id']))
elif entity_type in ['ou', 'org', 'ou_ns', 'org_ns', 'ou_susp', 'org_susp',]:
if entity_type in ['ou_ns', 'org_ns']:
@@ -14061,9 +14201,9 @@ def ProcessGAMCommand(args):
elif command == 'calendar':
argument = sys.argv[3].lower()
if argument == 'showacl':
doCalendarShowPrintACL(csvOut=False)
doCalendarPrintShowACLs(False)
elif argument == 'printacl':
doCalendarShowPrintACL(csvOut=True)
doCalendarPrintShowACLs(True)
elif argument == 'add':
doCalendarAddACL('Add')
elif argument in ['del', 'delete']:
@@ -14410,6 +14550,14 @@ def ProcessGAMCommand(args):
elif command == 'imap':
#doImap(users)
runCmdForUsers(doImap, users, default_to_batch=True)
elif command == 'sendemail':
sendOrDropEmail(users, 'send')
elif command == 'importemail':
sendOrDropEmail(users, 'import')
elif command == 'insertemail':
sendOrDropEmail(users, 'insert')
elif command == 'draftemail':
sendOrDropEmail(users, 'draft')
elif command == 'language':
doLanguage(users)
elif command in ['pop', 'pop3']:

View File

@@ -1,8 +1,7 @@
python-dateutil
dnspython
google-api-python-client
google-auth
google-auth-httplib2
google-auth-oauthlib
google-auth-oauthlib==0.4.0
httplib2>=0.13.0
passlib

View File

@@ -4,6 +4,8 @@ from xml.etree import ElementTree as ET
import requests
from html.parser import HTMLParser
import string
import sys
import json
import dateutil.parser
class MyHTMLParser(HTMLParser):
@@ -27,18 +29,18 @@ class MyHTMLParser(HTMLParser):
fullname = '%s %s' % (oem, model)
fullname = fullname.lower()
date = dateutil.parser.parse(data).replace(day=1).strftime('%Y-%m-%dT00:00:00.000Z')
output_rows.append(" '%s': '%s'," % (fullname, date))
output_rows[fullname] = date
if fullname in exceptions:
for value in exceptions[fullname]:
output_rows.append(" '%s': '%s'," % (value, date))
output_rows[value] = date
data_is_date = False
else:
model = ''.join(filter(lambda x: x in printable, data)).replace('"', '\\"')
model = ''.join(filter(lambda x: x in printable, data))
data_is_date = True
next_data_is_td = False
global oem, next_data_is_oem, next_data_is_td, data_is_date, model, printable, exceptions, output_rows
output_rows = []
output_rows = {}
printable = set(string.printable)
exceptions = {
# 'AUE OEM MODEL': ['API MODEL 1', ...]
@@ -95,10 +97,6 @@ next_data_is_oem = False
next_data_is_td = False
data_is_date = False
auepage = requests.get('https://support.google.com/chrome/a/answer/6220366?hl=en')
print('CROS_AUE_DATES = {')
parser = MyHTMLParser()
parser.feed(auepage.content.decode('utf-8'))
output_rows.sort(key=str.lower)
for row in output_rows:
print(row)
print('}')
print(json.dumps(output_rows, indent=2, sort_keys=True))

View File

@@ -2,22 +2,22 @@ echo "Installing Net-Framework-Core..."
export mypath=$(pwd)
until powershell Install-WindowsFeature Net-Framework-Core; do echo "trying again..."; done
cd ~/pybuild
export exefile=Win32OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
if [ ! -e $exefile ]; then
echo "Downloading $exefile..."
wget --quiet https://slproweb.com/download/$exefile
fi
echo "Installing $exefile..."
powershell ".\\${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl"
#export exefile=Win32OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
#if [ ! -e $exefile ]; then
# echo "Downloading $exefile..."
# wget --quiet https://slproweb.com/download/$exefile
#fi
#echo "Installing $exefile..."
#powershell ".\\${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl"
cinst -y --forcex86 python3
until cinst -y wixtoolset; do echo "trying again..."; done
echo "OpenSSL dlls..."
ls -alRF /c/ssl
echo "c drive"
ls -al /c/
echo "Python dlls..."
ls -al /c/Python37/DLLs
until cp -v /c/ssl/*.dll /c/Python37/DLLs; do echo "trying again..."; done
#echo "OpenSSL dlls..."
#ls -alRF /c/ssl
#echo "c drive"
#ls -al /c/
#echo "Python dlls..."
#ls -al /c/Python37/DLLs
#until cp -v /c/ssl/*.dll /c/Python37/DLLs; do echo "trying again..."; done
export PATH=$PATH:/c/Python37/scripts
cd $mypath
pip install --upgrade pip

View File

@@ -2,22 +2,22 @@ echo "Installing Net-Framework-Core..."
export mypath=$(pwd)
until powershell Install-WindowsFeature Net-Framework-Core; do echo "trying again..."; done
cd ~/pybuild
export exefile=Win64OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
if [ ! -e $exefile ]; then
echo "Downloading $exefile..."
wget --quiet https://slproweb.com/download/$exefile
fi
echo "Installing $exefile..."
powershell ".\\${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl"
#export exefile=Win64OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
#if [ ! -e $exefile ]; then
# echo "Downloading $exefile..."
# wget --quiet https://slproweb.com/download/$exefile
#fi
#echo "Installing $exefile..."
#powershell ".\\${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl"
cinst -y python3
until cinst -y wixtoolset; do echo "trying again..."; done
echo "OpenSSL dlls..."
ls -alRF /c/ssl
echo "c drive"
ls -al /c
echo "Python dlls..."
ls -al /c/Python37/DLLs
until cp -v /c/ssl/*.dll /c/Python37/DLLs; do echo "trying again..."; done
#echo "OpenSSL dlls..."
#ls -alRF /c/ssl
#echo "c drive"
#ls -al /c
#echo "Python dlls..."
#ls -al /c/Python37/DLLs
#until cp -v /c/ssl/*.dll /c/Python37/DLLs; do echo "trying again..."; done
export PATH=$PATH:/c/Python37/scripts
cd $mypath
pip install --upgrade pip

View File

@@ -1,3 +1,4 @@
import datetime
import re
import sys
from html.entities import name2codepoint
@@ -22,8 +23,7 @@ class _DeHTMLParser(HTMLParser):
self.__text.append(data)
def handle_charref(self, name):
self.__text.append(
chr(int(name[1:], 16)) if name.startswith('x') else chr(int(name)))
self.__text.append(chr(int(name[1:], 16)) if name.startswith('x') else chr(int(name)))
def handle_entityref(self, name):
cp = name2codepoint.get(name)
@@ -73,6 +73,16 @@ def indentMultiLineText(message, n=0):
return message.replace('\n', '\n{0}'.format(' ' * n)).rstrip()
def formatTimestampYMD(timestamp):
return datetime.datetime.fromtimestamp(int(timestamp)/1000).strftime('%Y-%m-%d')
def formatTimestampYMDHMS(timestamp):
return datetime.datetime.fromtimestamp(int(timestamp)/1000).strftime('%Y-%m-%d %H:%M:%S')
def formatTimestampYMDHMSF(timestamp):
return str(datetime.datetime.fromtimestamp(int(timestamp)/1000))
def formatFileSize(fileSize):
if fileSize == 0:
return '0kb'

View File

@@ -6,7 +6,7 @@ import platform
import re
gam_author = 'Jay Lee <jay0lee@gmail.com>'
gam_version = '4.87'
gam_version = '4.89'
gam_license = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
GAM_URL = 'https://git.io/gam'
@@ -20,7 +20,7 @@ GAM_RELEASES = 'https://github.com/jay0lee/GAM/releases'
GAM_WIKI = 'https://github.com/jay0lee/GAM/wiki'
GAM_ALL_RELEASES = 'https://api.github.com/repos/jay0lee/GAM/releases'
GAM_LATEST_RELEASE = GAM_ALL_RELEASES+'/latest'
GAM_PROJECT_APIS = 'https://raw.githubusercontent.com/jay0lee/GAM/master/src/project-apis.txt'
GAM_PROJECT_FILEPATH = 'https://raw.githubusercontent.com/jay0lee/GAM/master/'
true_values = ['on', 'yes', 'enabled', 'true', '1']
false_values = ['off', 'no', 'disabled', 'false', '0']
@@ -430,6 +430,18 @@ DOCUMENT_FORMATS_MAP = {
{'mime': 'application/vnd.oasis.opendocument.text', 'ext': '.odt'}],
}
DNS_ERROR_CODES_MAP = {
1: 'DNS Query Format Error',
2: 'Server failed to complete the DNS request',
3: 'Domain name does not exist',
4: 'Function not implemented',
5: 'The server refused to answer for the query',
6: 'Name that should not exist, does exist',
7: 'RRset that should not exist, does exist',
8: 'Server not authoritative for the zone',
9: 'Name not in zone'
}
EMAILSETTINGS_OLD_NEW_OLD_FORWARD_ACTION_MAP = {
'ARCHIVE': 'archive',
'DELETE': 'trash',
@@ -600,266 +612,6 @@ CROS_END_ARGUMENTS = ['end', 'enddate']
CROS_TPM_VULN_VERSIONS = ['41f', '420', '628', '8520',]
CROS_TPM_FIXED_VERSIONS = ['422', '62b', '8521',]
# parsed from https://support.google.com/chrome/a/answer/6220366?hl=en
# using src/tools/parse-aue.py
CROS_AUE_DATES = {
'acer ac700': '2016-08-01T00:00:00.000Z',
'acer c7 chromebook (c710)': '2017-10-01T00:00:00.000Z',
'acer c7 chromebook': '2017-10-01T00:00:00.000Z',
'acer c720 chromebook': '2019-06-01T00:00:00.000Z',
'acer c740 chromebook': '2019-06-01T00:00:00.000Z',
'acer chromebase 24': '2021-06-01T00:00:00.000Z',
'acer chromebase': '2020-08-01T00:00:00.000Z',
'acer chromebook 11 (c720, c720p)': '2019-06-01T00:00:00.000Z',
'acer chromebook 11 (c732, c732t, c732l, c732lt)': '2023-11-01T00:00:00.000Z',
'acer chromebook 11 (c740)': '2020-06-01T00:00:00.000Z',
'acer chromebook 11 (c771, c771t)': '2022-11-01T00:00:00.000Z',
'acer chromebook 11 (cb3-111, c730, c730e)': '2019-08-01T00:00:00.000Z',
'acer chromebook 11 (cb3-131, c735)': '2021-01-01T00:00:00.000Z',
'acer chromebook 11 (cb311-8h, cb311-8ht)': '2023-11-01T00:00:00.000Z',
'acer chromebook 11 n7 (c731, c731t)': '2022-01-01T00:00:00.000Z',
'acer chromebook 13 (cb5-311)': '2019-09-01T00:00:00.000Z',
'acer chromebook 13 (cb713-1w)': '2024-06-01T00:00:00.000Z',
'acer chromebook 13(cb5-311, c810)': '2019-09-01T00:00:00.000Z',
'acer chromebook 14 (cb3-431)': '2021-06-01T00:00:00.000Z',
'acer chromebook 14 for work (cp5-471)': '2022-11-01T00:00:00.000Z',
'acer chromebook 15 (c910 / cb5-571)': '2020-06-01T00:00:00.000Z',
'acer chromebook 15 (cb3-531)': '2020-06-01T00:00:00.000Z',
'acer chromebook 15 (cb3-532)': '2021-08-01T00:00:00.000Z',
'acer chromebook 15 (cb315-1h,cb315-1ht)': '2023-11-01T00:00:00.000Z',
'acer chromebook 15 (cb5-571, c910)': '2020-06-01T00:00:00.000Z',
'acer chromebook 15 (cb515-1h,cb515-1ht)': '2023-11-01T00:00:00.000Z',
'acer chromebook 311 (c721, c733, c733u, c733t)': '2025-06-01T00:00:00.000Z',
'acer chromebook 311': '2025-06-01T00:00:00.000Z',
'acer chromebook 311': '2025-06-01T00:00:00.000Z',
'acer chromebook 315 (cb315-2h)': '2025-06-01T00:00:00.000Z',
'acer chromebook 315': '2025-06-01T00:00:00.000Z',
'acer chromebook 512 (c851, c851t)': '2025-06-01T00:00:00.000Z',
'acer chromebook 514': '2023-11-01T00:00:00.000Z',
'acer chromebook 714 (cb714-1w / cb714-1wt)': '2024-06-01T00:00:00.000Z',
'acer chromebook 715 (cb715-1w / cb715-1wt)': '2024-06-01T00:00:00.000Z',
'acer chromebook r11 (cb5-132t, c738t)': '2021-06-01T00:00:00.000Z',
'acer chromebook r13 (cb5-312t)': '2021-09-01T00:00:00.000Z',
'acer chromebook spin 11 (cp311-h1, cp311-1hn)': '2023-11-01T00:00:00.000Z',
'acer chromebook spin 11 (r751t)': '2023-11-01T00:00:00.000Z',
'acer chromebook spin 13 (cp713-1wn)': '2024-06-01T00:00:00.000Z',
'acer chromebook spin 15 (cp315)': '2023-11-01T00:00:00.000Z',
'acer chromebook spin 311 (r721t)': '2025-06-01T00:00:00.000Z',
'acer chromebook spin 511 (r752t, r752tn)': '2025-06-01T00:00:00.000Z',
'acer chromebook spin 511': '2025-06-01T00:00:00.000Z',
'acer chromebook spin 512 (r851tn)': '2025-06-01T00:00:00.000Z',
'acer chromebook tab 10': '2023-08-01T00:00:00.000Z',
'acer chromebox cxi2 / cxv2': '2020-06-01T00:00:00.000Z',
'acer chromebox cxi2': '2020-06-01T00:00:00.000Z',
'acer chromebox cxi3': '2024-06-01T00:00:00.000Z',
'acer chromebox': '2019-09-01T00:00:00.000Z',
'aopen chromebase commercial': '2020-09-01T00:00:00.000Z',
'aopen chromebase mini': '2022-02-01T00:00:00.000Z',
'aopen chromebox commercial 2': '2024-06-01T00:00:00.000Z',
'aopen chromebox commercial': '2020-09-01T00:00:00.000Z',
'aopen chromebox mini': '2022-02-01T00:00:00.000Z',
'asi chromebook': '2020-06-01T00:00:00.000Z',
'asus chromebit cs10': '2020-11-01T00:00:00.000Z',
'asus chromebook c200': '2019-06-01T00:00:00.000Z',
'asus chromebook c200ma': '2019-06-01T00:00:00.000Z',
'asus chromebook c201pa': '2020-06-01T00:00:00.000Z',
'asus chromebook c201pa': '2020-06-01T00:00:00.000Z',
'asus chromebook c202sa': '2021-06-01T00:00:00.000Z',
'asus chromebook c204': '2025-06-01T00:00:00.000Z',
'asus chromebook c204': '2025-06-01T00:00:00.000Z',
'asus chromebook c213na': '2023-11-01T00:00:00.000Z',
'asus chromebook c223': '2023-11-01T00:00:00.000Z',
'asus chromebook c300': '2019-08-01T00:00:00.000Z',
'asus chromebook c300ma': '2019-08-01T00:00:00.000Z',
'asus chromebook c300sa / c301sa': '2021-06-01T00:00:00.000Z',
'asus chromebook c403': '2023-11-01T00:00:00.000Z',
'asus chromebook c423': '2023-11-01T00:00:00.000Z',
'asus chromebook c523': '2023-11-01T00:00:00.000Z',
'asus chromebook flip c100pa': '2020-07-01T00:00:00.000Z',
'asus chromebook flip c101pa': '2023-08-01T00:00:00.000Z',
'asus chromebook flip c213': '2023-11-01T00:00:00.000Z',
'asus chromebook flip c214': '2025-06-01T00:00:00.000Z',
'asus chromebook flip c302': '2022-11-01T00:00:00.000Z',
'asus chromebook flip c434': '2024-06-01T00:00:00.000Z',
'asus chromebook tablet ct100': '2023-08-01T00:00:00.000Z',
'asus chromebox (cn60)': '2019-09-01T00:00:00.000Z',
'asus chromebox 2 (cn62)': '2021-06-01T00:00:00.000Z',
'asus chromebox 3 (cn65)': '2024-06-01T00:00:00.000Z',
'asus chromebox 3': '2024-06-01T00:00:00.000Z',
'asus chromebox cn60': '2019-09-01T00:00:00.000Z',
'asus chromebox cn62': '2021-06-01T00:00:00.000Z',
'bobicus chromebook 11': '2020-06-01T00:00:00.000Z',
'chromebook 11 (c730 / cb3-111)': '2019-08-01T00:00:00.000Z',
'chromebook 11 (c735)': '2021-01-01T00:00:00.000Z',
'chromebook 15 (cb515 - 1ht / 1h)': '2023-11-01T00:00:00.000Z',
'chromebook 311 (c721)': '2025-06-01T00:00:00.000Z',
'chromebook pcm-116e': '2020-06-01T00:00:00.000Z',
'consumer chromebook': '2020-06-01T00:00:00.000Z',
'cr-48': '2015-12-01T00:00:00.000Z',
'crambo chromebook': '2020-06-01T00:00:00.000Z',
'ctl chromebook j41 / j41t': '2023-11-01T00:00:00.000Z',
'ctl chromebook nl7 / nl7t-360 / nl7tw-360': '2023-11-01T00:00:00.000Z',
'ctl chromebook nl7': '2023-11-01T00:00:00.000Z',
'ctl chromebook tab tx1': '2023-08-01T00:00:00.000Z',
'ctl chromebook tablet tx1 for education': '2023-08-01T00:00:00.000Z',
'ctl chromebox cbx1': '2024-06-01T00:00:00.000Z',
'ctl j2 / j4 chromebook': '2020-06-01T00:00:00.000Z',
'ctl j5 chromebook': '2021-08-01T00:00:00.000Z',
'ctl n6 education chromebook': '2020-06-01T00:00:00.000Z',
'ctl nl61 chromebook': '2021-08-01T00:00:00.000Z',
'dell chromebook 11 (3120)': '2020-06-01T00:00:00.000Z',
'dell chromebook 11 (3180)': '2022-05-01T00:00:00.000Z',
'dell chromebook 11 (5190)': '2023-11-01T00:00:00.000Z',
'dell chromebook 11 2-in-1 (3189)': '2022-05-01T00:00:00.000Z',
'dell chromebook 11 2-in-1 (5190)': '2023-11-01T00:00:00.000Z',
'dell chromebook 11': '2019-06-01T00:00:00.000Z',
'dell chromebook 13 (3380)': '2022-11-01T00:00:00.000Z',
'dell chromebook 13 (7310)': '2020-09-01T00:00:00.000Z',
'dell chromebook 3100 2-in-1': '2025-06-01T00:00:00.000Z',
'dell chromebook 3100': '2025-06-01T00:00:00.000Z',
'dell chromebook 3400': '2025-06-01T00:00:00.000Z',
'dell chromebox': '2019-09-01T00:00:00.000Z',
'dell inspiron chromebook 14 2-in-1 (7486)': '2024-06-01T00:00:00.000Z',
'edugear chromebook k': '2020-06-01T00:00:00.000Z',
'edugear chromebook m': '2020-06-01T00:00:00.000Z',
'edugear chromebook r': '2020-06-01T00:00:00.000Z',
'edugear cmt chromebook': '2021-08-01T00:00:00.000Z',
'edxis chromebook': '2020-06-01T00:00:00.000Z',
'edxis education chromebook': '2020-06-01T00:00:00.000Z',
'epik 11.6" chromebook elb1101': '2020-06-01T00:00:00.000Z',
'google chromebook pixel (2015)': '2020-06-01T00:00:00.000Z',
'google chromebook pixel': '2018-06-01T00:00:00.000Z',
'google cr-48': '2015-12-01T00:00:00.000Z',
'google pixel slate': '2024-06-01T00:00:00.000Z',
'google pixelbook': '2024-06-01T00:00:00.000Z',
'haier chromebook 11 c': '2021-08-01T00:00:00.000Z',
'haier chromebook 11 g2': '2020-09-01T00:00:00.000Z',
'haier chromebook 11': '2020-06-01T00:00:00.000Z',
'haier chromebook 11e': '2020-06-01T00:00:00.000Z',
'hexa chromebook pi': '2020-06-01T00:00:00.000Z',
'hisense chromebook 11': '2020-06-01T00:00:00.000Z',
'hp chromebook 11 1100-1199 / hp chromebook 11 g1': '2018-10-01T00:00:00.000Z',
'hp chromebook 11 2000-2099 / hp chromebook 11 g2': '2019-06-01T00:00:00.000Z',
'hp chromebook 11 2100-2199 / hp chromebook 11 g3': '2020-06-01T00:00:00.000Z',
'hp chromebook 11 2200-2299 / hp chromebook 11 g4/g4 ee': '2020-06-01T00:00:00.000Z',
'hp chromebook 11 g1': '2018-10-01T00:00:00.000Z',
'hp chromebook 11 g2': '2019-06-01T00:00:00.000Z',
'hp chromebook 11 g3': '2020-06-01T00:00:00.000Z',
'hp chromebook 11 g4/g4 ee': '2020-06-01T00:00:00.000Z',
'hp chromebook 11 g5 / hp chromebook 11-vxxx': '2021-07-01T00:00:00.000Z',
'hp chromebook 11 g5 ee': '2022-01-01T00:00:00.000Z',
'hp chromebook 11 g5': '2021-07-01T00:00:00.000Z',
'hp chromebook 11 g6 ee': '2023-11-01T00:00:00.000Z',
'hp chromebook 11 g7 ee': '2025-06-01T00:00:00.000Z',
'hp chromebook 11a g6 ee': '2025-06-01T00:00:00.000Z',
'hp chromebook 13 g1': '2022-11-01T00:00:00.000Z',
'hp chromebook 14 / hp chromebook 14 g5': '2023-11-01T00:00:00.000Z',
'hp chromebook 14 ak000-099 / hp chromebook 14 g4': '2021-09-01T00:00:00.000Z',
'hp chromebook 14 db0000-db0999': '2025-06-01T00:00:00.000Z',
'hp chromebook 14 g3': '2019-10-01T00:00:00.000Z',
'hp chromebook 14 g4': '2021-09-01T00:00:00.000Z',
'hp chromebook 14 g5': '2023-11-01T00:00:00.000Z',
'hp chromebook 14 x000-x999 / hp chromebook 14 g3': '2019-10-01T00:00:00.000Z',
'hp chromebook 14': '2019-06-01T00:00:00.000Z',
'hp chromebook 14a g5': '2025-06-01T00:00:00.000Z',
'hp chromebook 15 g1': '2024-06-01T00:00:00.000Z',
'hp chromebook x2 ': '2024-06-01T00:00:00.000Z',
'hp chromebook x360 11 g1 ee': '2023-11-01T00:00:00.000Z',
'hp chromebook x360 11 g2 ee': '2025-06-01T00:00:00.000Z',
'hp chromebook x360 14 g1': '2024-06-01T00:00:00.000Z',
'hp chromebook x360 14': '2024-06-01T00:00:00.000Z',
'hp chromebox cb1-(000-099) / hp chromebox g1/ hp chromebox for meetings': '2019-09-01T00:00:00.000Z',
'hp chromebox g1': '2019-09-01T00:00:00.000Z',
'hp chromebox g2': '2024-06-01T00:00:00.000Z',
'hp pavilion chromebook 14': '2018-02-01T00:00:00.000Z',
'jp sa couto chromebook': '2020-06-01T00:00:00.000Z',
'lava xolo chromebook': '2020-06-01T00:00:00.000Z',
'lenovo 100e chromebook 2nd gen mtk': '2025-06-01T00:00:00.000Z',
'lenovo 100e chromebook 2nd gen': '2025-06-01T00:00:00.000Z',
'lenovo 100e chromebook': '2023-11-01T00:00:00.000Z',
'lenovo 100s chromebook': '2020-09-01T00:00:00.000Z',
'lenovo 14e chromebook': '2025-06-01T00:00:00.000Z',
'lenovo 300e chromebook 2nd gen mtk': '2025-06-01T00:00:00.000Z',
'lenovo 300e chromebook 2nd gen': '2025-06-01T00:00:00.000Z',
'lenovo 300e chromebook': '2025-06-01T00:00:00.000Z',
'lenovo 500e chromebook 2nd gen': '2025-06-01T00:00:00.000Z',
'lenovo 500e chromebook': '2023-11-01T00:00:00.000Z',
'lenovo chromebook c330': '2022-06-01T00:00:00.000Z',
'lenovo chromebook s330': '2022-06-01T00:00:00.000Z',
'lenovo flex 11 chromebook': '2022-06-01T00:00:00.000Z',
'lenovo ideapad c330 chromebook': '2022-06-01T00:00:00.000Z',
'lenovo ideapad s330 chromebook': '2022-06-01T00:00:00.000Z',
'lenovo n20 chromebook': '2019-06-01T00:00:00.000Z',
'lenovo n21 chromebook': '2020-06-01T00:00:00.000Z',
'lenovo n22 chromebook': '2021-06-01T00:00:00.000Z',
'lenovo n23 chromebook': '2021-06-01T00:00:00.000Z',
'lenovo n23 yoga chromebook': '2022-06-01T00:00:00.000Z',
'lenovo n42 chromebook': '2021-06-01T00:00:00.000Z',
'lenovo thinkcentre chromebox': '2020-06-01T00:00:00.000Z',
'lenovo thinkpad 11e 3rd gen chromebook': '2021-06-01T00:00:00.000Z',
'lenovo thinkpad 11e 4th gen chromebook': '2023-11-01T00:00:00.000Z',
'lenovo thinkpad 11e chromebook (4th gen)/lenovo thinkpad yoga 11e chromebook (4th gen)': '2023-11-01T00:00:00.000Z',
'lenovo thinkpad 11e chromebook': '2019-06-01T00:00:00.000Z',
'lenovo thinkpad 13': '2022-11-01T00:00:00.000Z',
'lenovo thinkpad x131e chromebook': '2018-06-01T00:00:00.000Z',
'lenovo yoga c630 chromebook': '2024-06-01T00:00:00.000Z',
'lg chromebase (22cb25s)': '2020-06-01T00:00:00.000Z',
'lg chromebase (22cv241)': '2019-06-01T00:00:00.000Z',
'lumos education chromebook': '2020-06-01T00:00:00.000Z',
'm&a chromebook': '2020-06-01T00:00:00.000Z',
'mecer chromebook': '2020-06-01T00:00:00.000Z',
'mecer v2 chromebook': '2021-08-01T00:00:00.000Z',
'medion chromebook akoya s2013 ': '2020-06-01T00:00:00.000Z',
'medion chromebook s2015': '2020-06-01T00:00:00.000Z',
'multilaser chromebook m11c': '2021-08-01T00:00:00.000Z',
'ncomputing chromebook cx100': '2020-06-01T00:00:00.000Z',
'ncomputing chromebook cx110': '2020-06-01T00:00:00.000Z',
'nexian chromebook 11.6\"': '2020-06-01T00:00:00.000Z',
'pcmerge chromebook al116': '2023-11-01T00:00:00.000Z',
'pcmerge chromebookpcm-116e/pcm-116eb': '2020-06-01T00:00:00.000Z',
'pcmerge chromebookpcm-116t-432b': '2021-08-01T00:00:00.000Z',
'poin2 chromebook 11': '2020-06-01T00:00:00.000Z',
'poin2 chromebook 11c': '2022-03-01T00:00:00.000Z',
'poin2 chromebook 14': '2022-03-01T00:00:00.000Z',
'positivo chromebook c216b': '2021-08-01T00:00:00.000Z',
'positivo chromebook ch1190': '2020-06-01T00:00:00.000Z',
'prowise 11.6" entry line chromebook': '2020-06-01T00:00:00.000Z',
'prowise chromebook eduline': '2023-11-01T00:00:00.000Z',
'prowise chromebook entryline': '2020-06-01T00:00:00.000Z',
'prowise chromebook proline': '2021-08-01T00:00:00.000Z',
'prowise proline chromebook': '2021-08-01T00:00:00.000Z',
'rgs education chromebook': '2020-06-01T00:00:00.000Z',
'samsung chromebook - xe303': '2018-07-01T00:00:00.000Z',
'samsung chromebook 2 11\" - xe500c12': '2020-06-01T00:00:00.000Z',
'samsung chromebook 2 11\"': '2019-06-01T00:00:00.000Z',
'samsung chromebook 2 13\"': '2019-06-01T00:00:00.000Z',
'samsung chromebook 3': '2021-06-01T00:00:00.000Z',
'samsung chromebook plus (v2)': '2024-06-01T00:00:00.000Z',
'samsung chromebook plus': '2023-08-01T00:00:00.000Z',
'samsung chromebook pro': '2022-11-01T00:00:00.000Z',
'samsung chromebook series 5 550': '2017-05-01T00:00:00.000Z',
'samsung chromebook series 5': '2016-06-01T00:00:00.000Z',
'samsung chromebook': '2018-07-01T00:00:00.000Z',
'samsung chromebox series 3': '2018-03-01T00:00:00.000Z',
'sector 5 e1 rugged chromebook': '2020-06-01T00:00:00.000Z',
'sector 5 e3 chromebook': '2023-11-01T00:00:00.000Z',
'senkatel c1101 chromebook': '2020-06-01T00:00:00.000Z',
'thinkpad 11e chromebook 3rd gen (yoga/clamshell)': '2021-06-01T00:00:00.000Z',
'thinkpad 13 chromebook': '2022-11-01T00:00:00.000Z',
'toshiba chromebook 2 (2015 edition)': '2020-09-01T00:00:00.000Z',
'toshiba chromebook 2': '2020-06-01T00:00:00.000Z',
'toshiba chromebook': '2019-06-01T00:00:00.000Z',
'true idc chromebook 11': '2020-06-01T00:00:00.000Z',
'true idc chromebook': '2020-06-01T00:00:00.000Z',
'videonet chromebook bl10': '2020-06-01T00:00:00.000Z',
'videonet chromebook': '2020-06-01T00:00:00.000Z',
'viewsonic nmp660 chromebox': '2024-06-01T00:00:00.000Z',
'viglen chromebook 11': '2020-06-01T00:00:00.000Z',
'viglen chromebook 11c': '2023-11-01T00:00:00.000Z',
'viglen chromebook 360': '2021-08-01T00:00:00.000Z',
'xolo chromebook': '2020-06-01T00:00:00.000Z',
}
COLLABORATIVE_ACL_CHOICES = {
'members': 'ALL_MEMBERS',
'managersonly': 'MANAGERS_ONLY',