Compare commits

...

425 Commits
v5.24 ... v6.05

Author SHA1 Message Date
Jay Lee
537a926618 Update build.yml 2021-06-11 09:49:39 -04:00
Jay Lee
f791a59b1d GAM 6.05 2021-06-11 09:30:42 -04:00
Jay Lee
0b8e41f993 cleanup 2021-06-11 09:29:32 -04:00
Ross Scroggs
f540fa2a38 Unescape \r and \n in chatmessage text so multiline messages can be created from command line (#1387)
* Unescape \r and \n in chatmessage text so multiline messages can be created

* Bring gam report activity list up to date
2021-06-10 09:33:51 -04:00
Ross Scroggs
2d7bc2f34a Check that required arguments are present (#1386)
* Check that required arguments are present

* Correct chat message documentation
2021-06-07 15:56:09 -04:00
Jay Lee
c2dea0a4d7 add a few new user attribute types 2021-06-04 15:55:50 -04:00
Jay Lee
42cbfbf8ed Update macos-install.sh 2021-06-02 14:02:37 -04:00
Jay Lee
137e79b012 Update macos-install.sh 2021-06-02 13:49:34 -04:00
Jay Lee
5849ed3ecc Update build.yml 2021-06-02 10:04:30 -04:00
Ross Scroggs
d3dc1e1197 Add chat commands (#1384) 2021-05-27 21:07:55 -04:00
Jay Lee
c20f0bef44 GAM 6.04 2021-05-26 13:12:28 -04:00
Jay Lee
c572b6b182 fix win zip and MSI 2021-05-26 11:59:57 -04:00
Jay Lee
a1392dbf86 fix archive path 2021-05-26 10:53:06 -04:00
Jay Lee
4e719bab5e few build / install cleanups 2021-05-25 12:59:57 -04:00
Jay Lee
34b51ea64a GAM 6.03 2021-05-25 12:49:10 -04:00
Jay Lee
5a2a72f530 fix importlib_metadata on Python < 3.8 2021-05-25 12:27:52 -04:00
Jay Lee
2ea80c41ab retry a few more key operations 2021-05-25 11:49:44 -04:00
Jay Lee
6f987958e8 Print versions for more libraries 2021-05-25 10:39:14 -04:00
Jay Lee
ae4007aad5 re-enable legacy linux build 2021-05-25 10:09:47 -04:00
Jay Lee
c4401f8bd4 refine Chat calls 2021-05-25 09:32:02 -04:00
Jay Lee
0e7472de50 Initial Support for Google Chat API 2021-05-24 16:37:39 -04:00
Jay Lee
e998c78609 name artificat zip 2021-05-24 12:30:18 -04:00
Jay Lee
c30b92cd38 fix msi build 2021-05-24 11:23:45 -04:00
Jay Lee
2bf2d2aef7 try src/ 2021-05-24 11:07:39 -04:00
Jay Lee
cdc04b0803 fix gampath on win/mac 2021-05-24 10:48:42 -04:00
Jay Lee
5f5875acc1 fix distpath linux 2021-05-24 10:11:40 -04:00
Jay Lee
d306c5e0a3 try with extensions 2021-05-24 10:04:52 -04:00
Jay Lee
19a815cffe One file 2021-05-24 09:54:57 -04:00
Jay Lee
da0c559293 artifact wildcard attempt #3 2021-05-24 09:33:42 -04:00
Jay Lee
a2c91ef7b3 artifact wildcard attempt #2 2021-05-24 09:24:51 -04:00
Jay Lee
722b94ca32 no --universal2 argument yet 2021-05-24 09:00:18 -04:00
Jay Lee
299742fe03 reverting Actions changes for universal2 attempt 2021-05-24 08:56:25 -04:00
Ross Scroggs
3964cbf911 Add dynamic option to update cigroup (#1381)
Update print|show teamdrive documentation
2021-05-17 17:09:34 -04:00
Jay Lee
63e4947ad5 fix version 2021-05-12 08:36:10 -04:00
Jay Lee
e3cb13a414 youtoo 2021-05-11 14:46:24 -04:00
Jay Lee
01fec79d78 --universal2 2021-05-11 14:39:25 -04:00
Jay Lee
a7043a1359 disable rebuild of bootloader 2021-05-11 14:00:06 -04:00
Jay Lee
91a93ecd62 only set target arch on 32-bit Win 2021-05-11 13:20:40 -04:00
Jay Lee
c52fdf6395 preserve gam builds as Action artifact 2021-05-11 12:56:22 -04:00
Jay Lee
1d1dad4b30 switch to PyInstaller branch for Apple M1 support 2021-05-11 12:53:14 -04:00
Jay Lee
f07a57e478 no strip 2021-05-11 10:28:13 -04:00
Jay Lee
ebacd9b4b4 standardize pyinstaller arguments 2021-05-11 10:22:29 -04:00
Jay Lee
f010e59597 remove -F on Windows 2021-05-11 10:15:10 -04:00
Jay Lee
a184d7a8e0 list gampath 2021-05-11 09:15:16 -04:00
Jay Lee
807f54c549 wheel 2021-05-11 09:02:50 -04:00
Jay Lee
24684abc1d wheel 2021-05-11 08:56:17 -04:00
Jay Lee
1f1a49976c upgrade PyInstaller 2021-05-11 08:52:08 -04:00
Jay Lee
562fda3079 disable staticx for now 2021-05-10 09:30:53 -04:00
Jay Lee
05642f3c14 try w/o readlink 2021-05-10 09:06:53 -04:00
Jay Lee
251e2774aa no mkdir 2021-05-10 08:59:30 -04:00
Jay Lee
2089589d34 fix distpath 2021-05-10 08:53:19 -04:00
Jay Lee
c48b135c43 more debug decrypt 2021-05-10 08:44:45 -04:00
Jay Lee
70121a6ebf more output 2021-05-07 11:14:03 -04:00
Jay Lee
c23e53585a fix gam binary paths 2021-05-07 10:59:05 -04:00
Jay Lee
89e964163e fix dist paths 2021-05-07 10:55:42 -04:00
Jay Lee
0357774ba6 Test moving back to one-dir instead of one-file for PyInstaller 2021-05-07 10:48:36 -04:00
Ross Scroggs
93cf750249 Code cleanup; display role for group members (#1379)
* Code cleanup; display role for group members

* Standardize member and membertree output

Should dates be added to membergtree output?

* Use member_id to get subgroup, avoid call to convert email to id

* Only show role on top-level members

* Use v1beta1 for info user grouptree

* Update groups.py
2021-05-07 09:07:44 -04:00
Ross Scroggs
b712f7a344 Cloud Identity v1 only uses preferredMemberKey (#1378)
* Cloud Identity v1 only uses preferredMemberKey

* Document print labels

* Cleanup/bug fix info user grouptree; fix todrive for print labels

* Standardize write_csv_file call in print labels

* Use cloudidentity_beta for calls that process memberKey

* Code cleanup
2021-05-06 08:10:36 -04:00
Jay Lee
4159a5cbb8 test on Python 3.10-beta 2021-05-04 12:27:52 -04:00
Jay Lee
2e78a291d4 test on Python 3.10 2021-05-04 12:22:22 -04:00
Jay Lee
3f1705c2a5 test on Python 3.10 2021-05-04 12:20:45 -04:00
Jay Lee
bb1f5f7059 Merge branch 'main' of https://github.com/jay0lee/GAM into main 2021-05-04 11:57:33 -04:00
Jay Lee
75b7d0c419 Mmebership oops 2021-05-04 11:57:19 -04:00
Ross Scroggs
41a6c11c55 Handle TYPE_MESSAGE fields with durations or counts as a special case (#1375)
* Handle TYPE_MESSAGE fields with durations or counts as a special case

* Allow schema TYPE_ENUM field values with/without common prefix
2021-05-04 10:15:36 -04:00
Jay Lee
57d908e369 disable grouptree for now 2021-05-04 09:59:21 -04:00
Jay Lee
64274fdb33 more test fixes 2021-05-04 09:19:59 -04:00
Jay Lee
da919fd189 add few tests and fix one 2021-05-04 09:17:14 -04:00
Jay Lee
cfa25f12d3 6.02, admin.googleapis.com as test, MacOS universal2 build 2021-05-04 09:12:45 -04:00
Jay Lee
05bc1c1263 just stick with staic python versions 2021-05-04 08:38:56 -04:00
Jay Lee
939c79c37f use env variable 2021-05-04 08:32:06 -04:00
Jay Lee
d352ddeea1 use env variable 2021-05-04 08:30:56 -04:00
Jay Lee
72a683f2b1 Merge branches (#1377)
* Fix tests with apiclient >= 2.1

* disable MacOS 11 job

* info user grouptree and info cigroup membertree

* build updates
2021-05-04 08:12:35 -04:00
Ross Scroggs
784399f345 Handle TYPE_MESSAGE fields with durations or counts as a special case (#1374) 2021-05-02 08:22:29 -04:00
Ross Scroggs
710be4371b Fix typo (#1373) 2021-04-30 21:00:51 -04:00
Jay Lee
eece358aec Googleapiclient test fix (#1372)
* Fix tests with apiclient >= 2.1

* disable MacOS 11 job
2021-04-26 07:35:07 -04:00
Ross Scroggs
b43ada4f83 Add Cloud Search product name (#1370) 2021-04-23 09:02:15 -04:00
Jay Lee
9030af4faf Cloud Search SKU 2021-04-21 09:46:11 -04:00
Ross Scroggs
38b424b62e Add convertalias to delegate commands to convert aliases to primary (#1368)
* Add convertalias to delegate commands to convert aliases to primary

* New PyInstaller, won't build ARM without it
2021-04-21 09:38:07 -04:00
Jay Lee
1d9bf0b1aa Merge branch 'main' of https://github.com/jay0lee/GAM into main 2021-04-20 15:57:06 -04:00
Jay Lee
d3b7700c07 re-enable MacOS universal2 build 2021-04-20 15:56:27 -04:00
Ross Scroggs
d9513e159f Added support for localfile - in gam <UserTypeEntity> create|update drivefile (#1366)
This allows commands/programs to output data to stdout which can then be uploaded to a Google Drive file.
```
generatedata | gam user user@domain.com create drivefile drivefilename test.csv localfile - mimetype gsheet
```
2021-04-20 15:43:09 -04:00
Jay Lee
6ddfdf2514 print labels with counts 2021-04-20 15:37:21 -04:00
Jay Lee
478804bd5c Show/print full teamdrives info 2021-04-15 12:25:04 -04:00
Jay Lee
b61165a753 make sure we are using primary addresses for delegation 2021-04-10 21:28:19 -04:00
Ross Scroggs
b3814ae7be Document new Google Workspave Frontline license (#1363) 2021-04-08 16:42:07 -04:00
Jay Lee
019c363a74 Merge branch 'main' of https://github.com/jay0lee/GAM into main 2021-04-08 13:10:46 -04:00
Jay Lee
da5f80e704 Workspace Frontline SKU 2021-04-08 13:10:33 -04:00
Ross Scroggs
b37b10e669 Restandardize chromehistory columns; fix chromepolicy (#1362)
* Restandardize chromehistory columns; fix chromepolicy

* Update chromehistory.py
2021-04-08 11:27:30 -04:00
Jay Lee
8ca92eda39 G Suite > Workspace in few more spots 2021-04-08 09:38:37 -04:00
Jay Lee
81dbbc36db build channel and platform maps dynamically to reduce future maintenance 2021-04-08 09:08:04 -04:00
Jay Lee
7065101b87 further refine chromehistory output 2021-04-08 08:14:00 -04:00
Jay Lee
00c302e545 further refine chromehistory output 2021-04-08 08:12:15 -04:00
Ross Scroggs
703530ce7f Standardize chrome history column order; update data transfer apps (#1361) 2021-04-08 07:50:52 -04:00
Jay Lee
7ac15042d8 Merge branch 'main' of https://github.com/jay0lee/GAM into main 2021-04-07 15:27:32 -04:00
Jay Lee
a80ec52027 add more useful columns to chromehistory 2021-04-07 15:27:28 -04:00
Ross Scroggs
4da4132220 Validate chrome.users.chromebrowserupdates targetVersionPrefixSetting channel-offset (#1359)
* Validate chrome.users.chromebrowserupdates targetVersionPrefixSetting channel-offset

* Fix typo, add extended channel

Pass extended on to maintainer of :
https://developer.chrome.com/docs/versionhistory/reference/#channel-identifiers
2021-04-07 15:09:45 -04:00
Jay Lee
8682e66eb0 Update build.yml 2021-04-07 13:01:27 -04:00
Ross Scroggs
34bf205d37 Fix indentation (#1357) 2021-04-07 12:34:31 -04:00
Jay Lee
d6c2c6a2c3 Lazy load yubikey module to avoid lib errors when not in use 2021-04-07 09:27:13 -04:00
Jay Lee
f45639e6e2 switch User Invitations to DwD for now 2021-04-06 17:42:39 -04:00
Jay Lee
82968e29bf fix tests 2021-04-06 16:44:07 -04:00
Jay Lee
5d3d571545 Merge branch 'main' of https://github.com/jay0lee/GAM into main 2021-04-06 16:35:49 -04:00
Jay Lee
6999c13877 allow Chrome pinning to relative version like 'stable-1' 2021-04-06 16:35:34 -04:00
Ross Scroggs
82a551e88f Have whatis check for unmanaged accounts (#1355)
* Have whatis check for unmanaged accounts

* Handle addition error in whatis
2021-04-06 16:29:08 -04:00
Ross Scroggs
1b1a0c876c Implement Chrome version history (#1354)
* Implement Chrome version history

* Update GamCommands.txt

* Use httpObj
2021-04-06 14:08:27 -04:00
Ross Scroggs
b262c4a898 Implement Issue #1345 (#1352)
* Implement Issue #1345

* Clean up verifynotinvitable
2021-04-06 13:26:19 -04:00
Jay Lee
22d1055d82 allow i 2021-04-06 12:37:48 -04:00
Jay Lee
fe38565a9a 3.9.4 2021-04-06 12:06:57 -04:00
Jay Lee
a25d14e83f pin to google api client 2.0.2 for now 2021-04-06 11:56:51 -04:00
Jay Lee
15b21dd8d7 Merge branch 'main' of https://github.com/jay0lee/GAM into main 2021-04-06 09:18:13 -04:00
Jay Lee
caedcde49b Update build.yml 2021-04-04 19:23:29 -04:00
Ross Scroggs
8091e23e00 Implement Chrome Management API calls (#1350)
* Implement Chrome Management API calls

* User start/end in print chromeappdevices

* Handle a Chrome version without a version field
2021-04-02 14:44:58 -04:00
Jay Lee
08e1090b15 Update build.yml 2021-04-02 14:43:51 -04:00
Jay Lee
f76b5cb2eb Merge branch 'main' of https://github.com/jay0lee/GAM into main 2021-04-02 08:11:05 -04:00
Dima Scherbakov
edc4311dcb Bump google-api-python-client requirements to v2.0.0 (#1346)
We pass static_discovery keyword arg that got introduced in v2 only.
2021-03-28 15:55:56 -04:00
Jay Lee
a613bff664 Update build.yml 2021-03-25 14:36:09 -04:00
Jay Lee
8f875d2a9c Update build.yml 2021-03-25 14:35:49 -04:00
Jay Lee
fb60e0b389 enable chromemanagement reporting api 2021-03-25 11:01:14 -04:00
Ross Scroggs
2199fb2828 Add header to gam show chromepolicy to display OU and printerid/appid (#1341) 2021-03-23 16:16:58 -04:00
Ross Scroggs
b7d052a6b3 Match ENUM fields and descriptions (#1340) 2021-03-23 08:53:43 -04:00
Ross Scroggs
b333816dc8 Update policies and user invitations (#1339)
* Update policies and user invitations

Show chrome policy schemas in sorted order

Change create userintervention to send userintervention  to be consistent with API
Add state and orderby option to print userinvitations

* Sort polices in show chromepolicies
2021-03-22 09:06:04 -04:00
Ross Scroggs
90160da042 When displaying printers, add orgUnitPath (#1338) 2021-03-19 14:19:40 -04:00
Ross Scroggs
6f2ebf8d2d Add info printer command/ChromePolicy cleanup (#1337)
* Add info printer command

* ChromePolicy cleanup

Make update chromepolicy orgunit default to / like delete and print
Add `filter <String>` to print chromeschema
Make update_policy code to set additionalTargetKeys consistent with delete_policy

I left verb at print for chromepolicy/chromeschema

* When printing schemasa, use ":" instead of " - "

* Fix print policy indentation

* Chrome policy cleanup

orgunit must be specified
Use verb show, add verb print later

* Recognize all ou forms to exit from schema mode

* Don't assign multiple variables on same line
2021-03-19 12:46:07 -04:00
Jay Lee
a65635365e Merge branch 'main' of https://github.com/jay0lee/GAM into main 2021-03-18 15:43:21 -04:00
Jay Lee
0eee6979b0 limit namespaces based on id type, quote userinvitation emails 2021-03-18 15:43:07 -04:00
Ross Scroggs
ec796e9f84 Update chrome policy ducumentation (#1336) 2021-03-18 14:55:57 -04:00
Jay Lee
aaed2a6d86 GAM 6.0 2021-03-18 11:20:18 -04:00
Ross Scroggs
0ea7c500e1 Two changes (#1335)
Allow DATE as schema field type
Allow gam check isinvitable <EmailAddress>
2021-03-18 10:40:22 -04:00
Jay Lee
d90c884cf2 chromepolicy cleanup 2021-03-18 10:01:35 -04:00
Jay Lee
93700c01a8 some chromepolicy fixes (some) 2021-03-17 19:29:03 -04:00
Jay Lee
1df5662d4f invitation not invite 2021-03-17 14:43:01 -04:00
Jay Lee
338eeba944 fix v on Pyinstaller version 2021-03-17 12:59:30 -04:00
Jay Lee
9651e4abb1 move to latest PyInstaller commit 2021-03-17 12:56:44 -04:00
Ross Scroggs
ed1f3400ac Various small cleanups (#1332) 2021-03-13 13:14:24 -05:00
Ross Scroggs
e9d9353fbb Drop redundant call (#1331) 2021-03-13 12:16:47 -05:00
Jay Lee
00adf4ca46 fix circular import 2021-03-13 11:38:09 -05:00
Ross Scroggs
870fc27c72 Clean up printer commands/documentation (#1330)
* Clean up printer commands/documentation

driverless has to take value so it can be changed from true to false
Drop separate deleteprinters command, merge into delete printers

* Printer delete update

Allow a list of printer IDs
Drop cros from crosfile and croscsvfile to avoid confusion; add cros back when calling getUsersToModify
2021-03-13 11:26:30 -05:00
Jay Lee
bd38b7479f Chrome Policy rough draft, further customer_id standardization 2021-03-13 10:02:53 -05:00
Jay Lee
a567599eae Use true customer_id with licensing API 2021-03-13 09:31:03 -05:00
Jay Lee
5e6f9353c2 explicity state customer format for new APIs and cleanup as necessary 2021-03-12 16:20:20 -05:00
Jay Lee
7de1179b7e Merge branch 'main' of https://github.com/jay0lee/GAM into main 2021-03-12 16:04:37 -05:00
Jay Lee
ea7c80c3a1 don't set pageSize for printers.list 2021-03-12 16:03:43 -05:00
Ross Scroggs
f252f757f1 UserInvitations clean up (#1329)
* UserInvitations clean up

This version of Cloud Identity API wants C in customer
I commented it out in case the developers figure out that it's inconsistent with devices and groups

Delete extraneous code

* Update userinvitations.py
2021-03-12 16:03:04 -05:00
Jay Lee
b27c63d0d7 cleanup enabledasa.txt 2021-03-12 15:58:24 -05:00
Jay Lee
bcce1a4472 disable few test for now 2021-03-12 15:50:14 -05:00
Jay Lee
9d9655512d stop spamming logs with every printer model 2021-03-12 14:22:42 -05:00
Jay Lee
7af75f31e4 no filter 2021-03-12 11:34:39 -05:00
Jay Lee
83f02c377f CUPS Printer API commands 2021-03-12 11:11:58 -05:00
Jay Lee
ce4f74bc61 New User Invitation API 2021-03-11 17:07:13 -05:00
Jay Lee
66651d0eed devices calls fixed 2021-03-11 16:41:38 -05:00
Jay Lee
ec0e143361 import sleep 2021-03-11 15:29:30 -05:00
Jay Lee
250e0188f7 disable devices in actions during outage 2021-03-11 15:18:37 -05:00
Jay Lee
3123e472fc fix actions spacing 2021-03-11 14:41:51 -05:00
Jay Lee
c12f7f1123 wait_for_mailbox command to ensure user has mailbox before attempting Gmail commands. 2021-03-11 14:36:47 -05:00
Ross Scroggs
7e706518c5 Two updates (#1327)
* Add writerscanshare to file characteristics

* In print filelist, include counts for permissions, parents, owners
2021-03-11 14:01:50 -05:00
Jay Lee
d8ca573983 wait for new user mailbox create 2021-03-11 11:15:12 -05:00
Jay Lee
2225625cd8 don't use googleapiclient static files 2021-03-11 10:25:51 -05:00
Jay Lee
89f0f01fd2 updated creds for GH Actions 2021-03-11 10:18:47 -05:00
Jay Lee
a36282d114 Update __init__.py 2021-03-05 11:52:47 -05:00
Jay Lee
a8c92b7f9a Use official yubikey-manager 4.0 2021-03-04 15:43:05 -05:00
Ross Scroggs
f505dac8f3 Add new Google Workspace for Education SKUs (#1326) 2021-02-25 18:18:21 -05:00
Jay Lee
8e4730a3bd Update README.md 2021-02-25 12:09:20 -05:00
Jay Lee
b094bb344b Update requirements.txt 2021-02-24 14:37:02 -05:00
Jay Lee
2685aa049d Update __init__.py 2021-02-24 14:36:39 -05:00
Jay Lee
b738d57433 Update build.yml 2021-02-19 13:24:16 -05:00
Jay Lee
539b870754 Update build.yml 2021-02-19 12:24:21 -05:00
Jay Lee
abeb0998ea Update build.yml 2021-02-19 12:14:31 -05:00
Jay Lee
82faddd985 fix win python version 2021-02-19 12:13:44 -05:00
Jay Lee
b8084c270e Python 3.9.2 2021-02-19 11:59:18 -05:00
Jay Lee
22c7da420c Update build.yml
OpenSSL 1.1.1j
2021-02-16 20:44:37 -05:00
Ross Scroggs
45a3c89b0b Add ou <OrgUnitPath> to print browsers (#1324) 2021-02-15 21:58:08 -05:00
Ross Scroggs
8fc9e6d1ee Reissue PR #1315; avoid trap when command <CrOSCommand> missing from issuecommand (#1323) 2021-02-14 15:17:27 -05:00
Jay Lee
7f0b286d8e Allow "rotating" to a YubiKey private key 2021-02-14 20:01:14 +00:00
Jay Lee
4f664df087 VERSION hack no longer needed in .spec either 2021-02-12 15:33:50 +00:00
Jay Lee
dff48e3146 Use newer, less hacky ykman 2021-02-12 15:26:04 +00:00
Jay Lee
0fefa19f80 fix the hack 2021-02-11 21:31:42 +00:00
Jay Lee
88e07ddbaa avoid warnings about cryptography int_from_bytes 2021-02-11 20:37:15 +00:00
Jay Lee
44a3ef0d70 brew not homebrew 2021-02-11 19:39:16 +00:00
Jay Lee
5e793f171f Install swig and pyscard for MacOS 2021-02-11 19:32:20 +00:00
Jay Lee
e9bc63bee8 tell pyinstaller to manually include ykman/VERSION 2021-02-11 19:17:14 +00:00
Jay Lee
5636876e42 another attempt at Windows yubikey prereqs 2021-02-11 19:08:33 +00:00
Jay Lee
f2f7f549b0 install swig directly on Win 2021-02-11 18:46:57 +00:00
Jay Lee
1fc6e4f781 install yubikey-manager on Windows 2021-02-11 17:49:24 +00:00
Jay Lee
d641458fb4 uprev cache to force rebuilds 2021-02-11 17:26:16 +00:00
Jay Lee
517d44fa3c fix package name: 2021-02-11 17:22:41 +00:00
Jay Lee
80ee0bf9a8 install ykman prereqs 2021-02-11 17:19:23 +00:00
Jay Lee
0934b70414 add required Linux packages to install yubikey-manager 2021-02-11 16:49:00 +00:00
Jay Lee
f74168e2c7 Support for YubiKey private key storage 2021-02-11 16:38:19 +00:00
Ross Scroggs
bf4a6e6cde Fix bug in print courses ownerId is not converted to ownerEmail (#1316) 2021-02-07 14:38:51 -05:00
Jay Lee
0e09675779 fix "gam print browsertokens" with no arguments 2021-02-03 20:21:25 -05:00
Jay Lee
40e92ca3d2 stop building on Big Sur for now 2021-02-03 19:57:30 -05:00
Jay Lee
e776919bfd GAM 5.33 2021-02-03 19:55:22 -05:00
Jay Lee
84bfeffe46 handle usernames only when GC_DOMAIN not set 2021-02-03 19:42:26 -05:00
Jay Lee
1360abbecb say whether GH requests are auth or unauth 2021-02-03 16:32:50 -05:00
Jay Lee
2a13accfe4 Use GHCLIENT env variable if avail 2021-02-03 16:16:03 -05:00
Jay Lee
e26dac3993 Windows support for gam-install.sh 2021-02-03 15:16:07 -05:00
Jay Lee
1b7a43e82b avoid building cd for user commands 2021-02-03 13:32:44 -05:00
Ross Scroggs
141aca9e25 Handle issue #1277 (#1312) 2021-01-28 12:10:56 -05:00
Jay Lee
4f99eb6f07 disable contact delegation test until we regen oauth2.txt for test accounts 2021-01-19 08:22:59 -05:00
Jay Lee
81075bb000 GAM 5.32, test contact delegation 2021-01-19 08:14:26 -05:00
Jay Lee
33057faaab include contact delegation discovery in pyinstaller exe 2021-01-19 07:53:09 -05:00
Ross Scroggs
28b831c6a2 Fix bug in calendar delete ACL, code assumed a role was included (#1308)
* Fix bug in calendar delete ACL, code assumed a role was included

Update documentation

* Add support for Chrome Browser Enrollment Tokens

create browsertoken always returns  `ERROR: 400: Invalid Input - invalid`
Maybe you can figure out what's going on
2021-01-18 17:03:58 -05:00
Jay Lee
ef4d3d2659 Update build.yml 2021-01-13 11:37:53 -05:00
Ross Scroggs
09c0c18fce Add GAM_CSV_ROW_DROP_FILTER (#1304)
* Add GAM_CSV_ROW_DROP_FILTER

Allow regex column names in GAM_CSV_ROW_FILTER and GAM_CSV_ROW_DROP_FILTER.
```
# Get users with managers
$ export GAM_CSV_ROW_FILTER="relations.*.type:regex:manager"
$ gam print users query "orgUnitPath='/Test'" relations
Getting all Users in G Suite account that match query (orgUnitPath='/Test') (may take some time on a large account)...
Got 17 Users: dick@domain.com - tom@domain.com
primaryEmail,relations,relations.0.value,relations.0.type,relations.1.value,relations.1.type
testuser1@domain.com,,admin@rdschool.net,manager,,
testuser2@domain.com,,testuser1@domain.com,manager,,

# Get users without managers
$ export GAM_CSV_ROW_DROP_FILTER="relations.*.type:regex:manager"
$ gam print users query "orgUnitPath='/Test'" relations
Getting all Users in G Suite account that match query (orgUnitPath='/Test') (may take some time on a large account)...
Got 17 Users: dick@domain.com - tom@domain.com
primaryEmail,relations,relations.0.value,relations.0.type,relations.1.value,relations.1.type
dick@domain.com,,,,,
harry@domain.com,,,,,
testadmin@domain.com,,,,,
...

* Update var.py to 5.31
2021-01-13 11:37:17 -05:00
Jay Lee
ff80150216 Update build.yml 2021-01-07 12:25:23 -05:00
Jay Lee
a8203baa50 another attempt at arm64 MacOS support 2021-01-06 09:20:10 -05:00
Jay Lee
aa33dc83d4 Use universal2 Python on Big Sur 2021-01-03 19:10:55 -05:00
Jay Lee
4155e2bb64 Update build.yml 2021-01-03 17:44:17 -05:00
Jay Lee
9660cafa99 allow OpenSSL 1.1.1g since python.org Python uses 2021-01-03 13:21:19 -05:00
Jay Lee
c55b9cfe96 new cache to force rebuilds 2021-01-03 13:16:45 -05:00
Jay Lee
78ea96767e Update build.yml 2021-01-03 13:13:50 -05:00
Jay Lee
d11d7a8ffc Universal2 build for Native Apple M1 CPU support 2021-01-03 13:10:12 -05:00
Jay Lee
bec789d2fb GAM 5.31 2021-01-03 12:52:01 -05:00
Ross Scroggs
0cda3fca31 More expireTime updates (#1299) 2020-12-31 11:38:14 -05:00
Jay Lee
4f8980184f Update build.yml 2020-12-31 11:35:33 -05:00
Tim Gates
ffa096d988 docs: fix simple typo, sysyem -> system (#1300)
There is a small typo in src/gam/controlflow.py.

Should read `system` rather than `sysyem`.
2020-12-27 20:33:11 -05:00
Ross Scroggs
6e1b1ed9d5 expireTime can now be updated (#1298) 2020-12-21 20:42:06 -05:00
Ross Scroggs
48526b815e Update GamCommands.txt (#1297) 2020-12-17 07:53:03 -05:00
Jay Lee
1ded893e7b Update build.yml 2020-12-16 20:41:35 -05:00
Jay Lee
c61bd01c0f Update build.yml 2020-12-16 20:38:49 -05:00
Jay Lee
f0adcc90c7 Update build.yml 2020-12-16 20:30:37 -05:00
Jay Lee
2abb13bb4c Update build.yml 2020-12-16 15:35:30 -05:00
Ross Scroggs
8f69c4c820 Make contact delegation consistent with email delegation (#1296)
Add auth to discovery document
uid allowed in create/delete as input is converted to primaryemail
2020-12-15 15:22:23 -05:00
Jay Lee
5652c52d96 Update build.yml 2020-12-14 21:55:55 -05:00
Jay Lee
ff29cc192e Update build.yml 2020-12-14 20:54:58 -05:00
Jay Lee
7428d0e734 Update build.yml 2020-12-14 20:51:32 -05:00
Jay Lee
caad9e999c also check on contact delegation delete 2020-12-14 14:49:34 +00:00
Jay Lee
24cb225381 remove arm attempt for now 2020-12-14 14:29:46 +00:00
Jay Lee
2e3195c5ee prevent bad contact delegations 2020-12-14 14:26:22 +00:00
Jay Lee
bfa039a612 Update build.yml 2020-12-12 17:24:15 -05:00
Jay Lee
49f8988912 Update build.yml 2020-12-12 17:22:53 -05:00
Jay Lee
7e214dbe3b Update build.yml 2020-12-12 17:19:21 -05:00
Jay Lee
42ad12d8d8 Update build.yml 2020-12-12 17:16:39 -05:00
Jay Lee
842e6ef788 Update build.yml 2020-12-12 17:15:27 -05:00
Jay Lee
56b87039c2 Update build.yml
attempt to build on aarch64 and armv6
2020-12-12 17:14:25 -05:00
Ross Scroggs
1767a0889d Handle updating individual annotated fields (#1294)
* Handle updating inv=dividual annotated fields

* Update cbcm.py
2020-12-11 10:07:37 -05:00
Ross Scroggs
3036366de5 Fix browser error messages, item name map in update browser (#1293)
* Update cbcm.py

Fix error message
$ gam update browser a27590cb-61fc-4ca3-8ef7-34bf736c4973 asset FileMakerServer

ERROR: asset is not a valid argument for "gam print browsers"

Fix item name map
$ gam update browser a27590cb-61fc-4ca3-8ef7-34bf736c4973 assetid FileMakerServer location Location notes Notes user User

ERROR: 400: Invalid JSON payload received. Unknown name "annotatedAssetid" at 'browser': Cannot find field. - invalid

* Fix more error messages
2020-12-11 09:20:03 -05:00
Jay Lee
a8f1031e0f Update README.md 2020-12-09 13:45:47 -05:00
Jay Lee
054107c3b9 Update README.md 2020-12-09 13:45:32 -05:00
Jay Lee
c478b22ab9 Update README.md 2020-12-09 13:45:17 -05:00
Jay Lee
2f712499ea Update README.md 2020-12-09 13:44:43 -05:00
Jay Lee
39b9622cdb Support for Contact Delegation API 2020-12-09 11:34:05 -05:00
Jay Lee
59a3a68357 Update gam-install.sh 2020-12-08 12:23:04 -05:00
Jay Lee
d920dbd79e Update build.yml 2020-12-08 10:49:38 -05:00
Jay Lee
49dd390c6b Update build.yml 2020-12-08 10:44:02 -05:00
Jay Lee
d20b4bc334 Update build.yml 2020-12-08 10:41:09 -05:00
Jay Lee
a973807e3e Update macos-before-install.sh 2020-12-08 10:06:53 -05:00
Jay Lee
431b5f4f30 Update macos-install.sh 2020-12-08 10:03:56 -05:00
Jay Lee
636799e567 Update macos-install.sh 2020-12-08 09:48:22 -05:00
Jay Lee
d003e3fa1b Update macos-install.sh 2020-12-08 09:39:21 -05:00
Jay Lee
07edbe6619 Update macos-install.sh 2020-12-08 09:32:04 -05:00
Jay Lee
dc4a5a05fe Update macos-before-install.sh 2020-12-08 09:29:29 -05:00
Jay Lee
a8c86eb53d Update macos-before-install.sh 2020-12-08 09:21:21 -05:00
Jay Lee
8ef9a62dc9 Update macos-install.sh 2020-12-08 08:28:40 -05:00
Jay Lee
29c9ed4135 Update macos-install.sh 2020-12-08 08:24:01 -05:00
Jay Lee
0426a4ca0d Update macos-install.sh 2020-12-08 08:16:42 -05:00
Jay Lee
af7bbe3cca Update macos-install.sh 2020-12-08 07:52:25 -05:00
Jay Lee
3c55153752 Update macos-install.sh 2020-12-08 07:42:36 -05:00
Jay Lee
a82ff4bb4e Update macos-before-install.sh 2020-12-07 22:36:10 -05:00
Jay Lee
73759e9611 Update macos-before-install.sh 2020-12-07 22:32:12 -05:00
Jay Lee
71d6d08f5a Update build.yml 2020-12-07 22:16:25 -05:00
Jay Lee
2f2be201a7 Update build.yml 2020-12-07 21:49:35 -05:00
Jay Lee
49aaca7172 Update build.yml 2020-12-07 21:47:06 -05:00
Jay Lee
f29c64984b Update macos-before-install.sh 2020-12-07 20:56:40 -05:00
Jay Lee
a50329edf3 Update macos-before-install.sh 2020-12-07 20:51:32 -05:00
Jay Lee
15d163abf4 Merge branch 'master' of https://github.com/jay0lee/GAM 2020-12-07 10:07:23 -05:00
Jay Lee
852a116c9d move per-OS build scripts out of travis folder 2020-12-07 10:07:11 -05:00
Ross Scroggs
e9c18d0c01 Add info deviceuserstate (#1290)
* Fix typos in update deviceuserstate documentation

* Add info deviceuserstate

* Update info/update deviceusertstate documentation

* Update devices.py
2020-12-07 09:38:05 -05:00
Jay Lee
5e6d4ecb1c rearrange issue types 2020-12-07 09:33:22 -05:00
Jay Lee
97635311db Update issue templates 2020-12-07 09:28:46 -05:00
Jay Lee
3c7ddfd236 Update build.yml 2020-12-07 08:49:18 -05:00
Jay Lee
e0aa0eb4a3 Update build.yml 2020-12-07 08:46:45 -05:00
Jay Lee
8eba9d252f Update build.yml 2020-12-07 08:45:05 -05:00
Jay Lee
5966680a31 Update README.md 2020-12-07 08:41:43 -05:00
Jay Lee
eb1563b1e7 Goodbye Travis, thanks for all the fish 2020-12-07 08:40:09 -05:00
Ross Scroggs
84f52668b7 update deviceuserstate cleanup/documentation (#1289)
* update deviceuserstate cleanup

* Update GamCommands.txt
2020-12-06 22:24:48 -05:00
Ross Scroggs
cc60095344 Map / to %2F in group email address for Group Settings API (#1288)
* Map / to %2F in group email address for Group Settings API

* Make update deviceuserstate consistent with other deviceuser commands

gam update deviceuserstate [id] <DeviceUserID> ...
2020-12-06 20:56:49 -05:00
Jay Lee
5a1f237b30 make vaultcount a print command 2020-12-06 16:17:00 -05:00
Jay Lee
934a671344 "gam show vaultcount" - fixes #1271 2020-12-06 16:08:25 -05:00
Jay Lee
b81ea8e8c7 Merge branch 'master' of https://github.com/jay0lee/GAM 2020-12-06 10:58:29 -05:00
Jay Lee
817920940e Fix crash on no device users 2020-12-06 10:58:01 -05:00
Jay Lee
e0f7ebbcba Update gam.spec 2020-12-06 10:08:19 -05:00
Ross Scroggs
8ce18960fe 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
2020-12-05 21:54:26 -05:00
Jay Lee
6a879927a7 rebuild cache 2020-12-05 20:57:22 -05:00
Jay Lee
9c22114aa5 remove invalid scope 2020-12-05 20:40:03 -05:00
Jay Lee
add9bef046 fix spec 2020-12-05 20:36:28 -05:00
Jay Lee
64b6cfea93 forgot the delete 2020-12-05 20:27:29 -05:00
Jay Lee
c8e76d5727 CBCM API, Device API update Client State, v5.30 2020-12-05 19:44:48 -05:00
Jay Lee
4fda0b6aaa actions take 129 2020-12-05 12:12:50 -05:00
Jay Lee
4634237879 actions take 128 2020-12-05 12:07:09 -05:00
Jay Lee
5cc91fef53 actions take 127 2020-12-05 11:27:05 -05:00
Jay Lee
4a3c408ec5 actions take 126 2020-12-05 11:21:52 -05:00
Jay Lee
23f0c55053 actions take 125 2020-12-05 10:59:19 -05:00
Jay Lee
e1cf21328a actions take 124 2020-12-05 10:53:18 -05:00
Jay Lee
7ce5f982b3 actions take 123 2020-12-05 10:42:21 -05:00
Jay Lee
8fb9439eff actions take 122 2020-12-05 10:18:59 -05:00
Jay Lee
700e348dbd actions take 121 2020-12-05 10:06:40 -05:00
Jay Lee
91a86a8663 actions take 120 2020-12-05 09:58:36 -05:00
Jay Lee
293270c03d actions take 119 2020-12-05 09:38:51 -05:00
Jay Lee
af13113161 actions take 118 2020-12-05 09:00:30 -05:00
Jay Lee
59b9dca5ec actions take 117 2020-12-04 21:32:46 -05:00
Jay Lee
bc5d4e8efb actions take 116 2020-12-04 21:23:15 -05:00
Jay Lee
91111a62f6 actions take 115 2020-12-04 21:03:02 -05:00
Jay Lee
5525324620 actions take 114 2020-12-04 20:48:54 -05:00
Jay Lee
29bd632e45 actions take 113 2020-12-04 20:33:13 -05:00
Jay Lee
369e3c7269 actions take 112 2020-12-04 20:23:57 -05:00
Jay Lee
383fed7b3d actions take 111 2020-12-04 20:14:45 -05:00
Jay Lee
849dd8d436 actions take 110 2020-12-04 19:56:55 -05:00
Jay Lee
6340a35cf2 actions take 109 2020-12-04 19:46:04 -05:00
Jay Lee
1b2ea1d4bd actions take 108 2020-12-04 19:41:22 -05:00
Jay Lee
9087872bc2 actions take 107 2020-12-04 19:36:23 -05:00
Jay Lee
d914e06f42 actions take 106 2020-12-04 19:14:29 -05:00
Jay Lee
e12af0c870 actions take 105 2020-12-04 19:01:07 -05:00
Jay Lee
b0b9e2a7de actions take 104 2020-12-04 18:58:06 -05:00
Jay Lee
5b88d8b9ca actions take 103 2020-12-04 18:50:29 -05:00
Jay Lee
b7010b099f actions take 102 2020-12-04 18:46:32 -05:00
Jay Lee
5484d39d90 actions take 101 2020-12-04 18:44:52 -05:00
Jay Lee
181a2f8949 actions take 100 2020-12-04 18:41:47 -05:00
Jay Lee
cee0f97850 actions take 99 2020-12-04 18:33:54 -05:00
Jay Lee
fd91388b7a actions take 98 2020-12-04 18:29:41 -05:00
Jay Lee
beae08a99d actions take 97 2020-12-04 18:27:20 -05:00
Jay Lee
aef614aeae actions take 96 2020-12-04 18:18:48 -05:00
Jay Lee
c87bc39ad8 actions take 95 2020-12-04 18:15:24 -05:00
Jay Lee
d0b78f8e81 actions take 94 2020-12-04 18:09:20 -05:00
Jay Lee
f126cc1d6c actions take 93 2020-12-04 17:50:22 -05:00
Jay Lee
2d8c3427c4 actions take 92 2020-12-04 17:45:32 -05:00
Jay Lee
c2acd926af actions take 91 2020-12-04 17:42:05 -05:00
Jay Lee
b904d77497 actions take 90 2020-12-04 17:40:05 -05:00
Jay Lee
c093b92c0b actions take 89 2020-12-04 16:27:17 -05:00
Jay Lee
734b8bfd40 actions take 87 2020-12-04 16:09:26 -05:00
Jay Lee
e337d1f116 actions take 87 2020-12-04 16:01:30 -05:00
Jay Lee
8434ac1e2f actions take 85 2020-12-04 15:55:16 -05:00
Jay Lee
349cdbf582 actions take 84 2020-12-04 15:53:19 -05:00
Jay Lee
693aeae9e5 actions take 83 2020-12-04 15:27:24 -05:00
Jay Lee
e4ea8e156d actions take 82 2020-12-04 15:07:20 -05:00
Jay Lee
ca2b7dd674 actions take 81 2020-12-04 14:31:35 -05:00
Jay Lee
8a744aa7fc actions take 80 2020-12-04 14:29:15 -05:00
Jay Lee
7f2beb4d80 actions take 79 2020-12-04 14:24:25 -05:00
Jay Lee
103c421b31 actions take 78 2020-12-04 14:21:19 -05:00
Jay Lee
0f4238e9a7 actions take 78 2020-12-04 14:15:59 -05:00
Jay Lee
c9508d2dac actions take 77 2020-12-04 14:00:22 -05:00
Jay Lee
5d293b4318 actions take 76 2020-12-04 13:49:40 -05:00
Jay Lee
3f4b814c0b actions take 75 2020-12-04 13:48:28 -05:00
Jay Lee
aca71d8db1 actions take 74 2020-12-04 13:08:19 -05:00
Jay Lee
87dbe3c945 actions take 73 2020-12-04 12:43:49 -05:00
Jay Lee
c254fc946f actions take 72 2020-12-04 12:41:24 -05:00
Jay Lee
5a4718eae8 actions take 71 2020-12-04 12:36:57 -05:00
Jay Lee
935c52f291 actions take 70 2020-12-04 12:25:40 -05:00
Jay Lee
04fe93d3b8 actions take 69 2020-12-04 12:21:49 -05:00
Jay Lee
22f279e309 actions take 68 2020-12-04 12:07:00 -05:00
Jay Lee
a0cff87e5f actions take 67 2020-12-04 12:02:06 -05:00
Jay Lee
943d327975 actions take 66 2020-12-04 11:55:11 -05:00
Jay Lee
6c4aced95e actions take 65 2020-12-04 11:52:43 -05:00
Jay Lee
ad80fd2a91 actions take 64 2020-12-04 09:44:16 -05:00
Jay Lee
43d50734a4 actions take 63 2020-12-04 09:29:19 -05:00
Jay Lee
52d6057365 actions take 62 2020-12-04 09:20:39 -05:00
Jay Lee
8dd5a5dd8a actions take 61 2020-12-04 09:19:20 -05:00
Jay Lee
4799b33e0e actions take 60 2020-12-04 09:16:52 -05:00
Jay Lee
d25ae7de81 actions take 59 2020-12-04 09:15:24 -05:00
Jay Lee
83cbbbf0b7 actions take 58 2020-12-04 09:13:43 -05:00
Jay Lee
4794578688 actions take 57 2020-12-04 09:12:34 -05:00
Jay Lee
446da392d9 actions take 56 2020-12-04 09:10:21 -05:00
Jay Lee
07cbd4cdbb actions take 56 2020-12-04 09:09:09 -05:00
Jay Lee
f17cbdc111 actions take 55 2020-12-04 09:03:11 -05:00
Jay Lee
28c5d277a0 actions take 55 2020-12-04 08:59:51 -05:00
Jay Lee
939840b702 actions take 54 2020-12-04 05:12:19 -05:00
Jay Lee
78485ae4e9 actions take 53 2020-12-04 05:09:24 -05:00
Jay Lee
cc47a93872 actions take 52 2020-12-04 05:05:25 -05:00
Jay Lee
af122eeb5c actions take 51 2020-12-04 04:51:46 -05:00
Jay Lee
ec118968ed actions take 50 2020-12-04 04:50:12 -05:00
Jay Lee
2de416fe81 actions take 49 2020-12-04 04:49:05 -05:00
Jay Lee
d10a3b91e3 actions take 48 2020-12-04 04:46:39 -05:00
Jay Lee
cdadf68f30 actions take 47 2020-12-04 04:37:37 -05:00
Jay Lee
900a123141 actions take 46 2020-12-04 04:35:20 -05:00
Jay Lee
4b2f9488ce actions take 45 2020-12-04 04:31:34 -05:00
Jay Lee
02d7f45988 actions take 44 2020-12-03 21:29:48 -05:00
Jay Lee
cc34dbb88e actions take 43 2020-12-03 21:23:47 -05:00
Jay Lee
5e9d99083c actions take 43 2020-12-03 21:21:23 -05:00
Jay Lee
295bf74a1b actions take 42 2020-12-03 21:16:59 -05:00
Jay Lee
7928437dc6 actions take 41 2020-12-03 21:10:59 -05:00
Jay Lee
83283b7b6b actions take 40 2020-12-03 21:08:40 -05:00
Jay Lee
09a289b4c4 actions take 39 2020-12-03 21:05:22 -05:00
Jay Lee
6b940b9d01 actions take 38 2020-12-03 21:01:46 -05:00
Jay Lee
cb492e0183 actions take 37 2020-12-03 20:41:26 -05:00
Jay Lee
92799d57ae actions take 36 2020-12-03 20:40:24 -05:00
Jay Lee
066100f218 actions take 35 2020-12-03 20:06:02 -05:00
Jay Lee
cd4dd44004 actions take 34 2020-12-03 20:02:32 -05:00
Jay Lee
40a2fdb7fd actions take 33 2020-12-03 16:34:26 -05:00
Jay Lee
b60cf11668 actions take 32 2020-12-03 15:41:42 -05:00
Jay Lee
0fa617c580 actions take 31 2020-12-03 15:39:55 -05:00
Jay Lee
1cfa08612e actions take 30 2020-12-03 15:38:57 -05:00
Jay Lee
2cebef9d4b actions take 29 2020-12-03 15:37:40 -05:00
Jay Lee
c75313cdf4 actions take 28 2020-12-03 14:33:12 -05:00
Jay Lee
ae1eaac037 actions take 27 2020-12-03 14:27:25 -05:00
Jay Lee
01465a898a actions take 26 2020-12-03 14:26:06 -05:00
Jay Lee
d6a7917ffd actions take 25 2020-12-03 14:04:23 -05:00
Jay Lee
d9946088ab actions take 24 2020-12-03 13:46:23 -05:00
Jay Lee
a2ad6a1037 actions take 23 2020-12-03 13:43:23 -05:00
Jay Lee
34e240b40a actions take 22 2020-12-03 12:42:36 -05:00
Jay Lee
ca1f33ade6 actions take 21 2020-12-03 12:26:03 -05:00
Jay Lee
d8bddb1c21 actions take 20 2020-12-03 12:05:25 -05:00
Jay Lee
64bab14483 actions take 19 2020-12-03 11:55:37 -05:00
Jay Lee
2d1830f4fc actions take 18 2020-12-03 11:53:00 -05:00
Jay Lee
ab00f2bd42 actions take 17 2020-12-03 11:50:30 -05:00
Jay Lee
6d9505a4c0 actions take 16 2020-12-03 11:42:38 -05:00
Jay Lee
40e3cb8ce5 actions take 15 2020-12-03 11:40:39 -05:00
Jay Lee
4783ec6696 actions take 14 2020-12-03 11:36:52 -05:00
Jay Lee
3bd746fe91 actions take 13 2020-12-03 11:36:09 -05:00
Jay Lee
a91a82eecc actions take 12 2020-12-03 11:34:23 -05:00
Jay Lee
a9564583cb actions take 11 2020-12-03 11:33:01 -05:00
Jay Lee
f988c8879e actions take 10 2020-12-03 11:28:29 -05:00
Jay Lee
825cad81a2 actions take 9 2020-12-03 11:22:58 -05:00
Jay Lee
b9c0ea065a actions take 8 2020-12-03 11:07:52 -05:00
Jay Lee
50ef633573 actions take 7 2020-12-03 11:07:05 -05:00
Jay Lee
10202df7d7 actions take 6 2020-12-03 11:05:31 -05:00
Jay Lee
a7815b41db actions take 5 2020-12-03 11:03:45 -05:00
Jay Lee
cf467eb868 actions take 4 2020-12-03 10:54:35 -05:00
Jay Lee
a3be19154f actions take 3 2020-12-03 10:53:11 -05:00
Jay Lee
a7c19c689c actions take 2 2020-12-03 10:50:54 -05:00
Jay Lee
1c78e3aac0 first test for GitHub actions 2020-12-03 10:47:26 -05:00
Jay Lee
b9cc3d77b3 recent commit that improves PyInstaller support for 3.9 2020-11-11 09:10:52 -05:00
Jay Lee
1feb81adf3 Update project-apis.txt 2020-11-03 14:38:17 -05:00
Jay Lee
fd937758e6 Merge branch 'master' of https://github.com/jay0lee/GAM 2020-10-15 17:40:39 -04:00
Jay Lee
fcc3d674c2 command payload value is a JSON string, not a dict 2020-10-15 17:40:26 -04:00
Jay Lee
ce74264a01 Update .travis.yml 2020-10-15 11:23:15 -04:00
59 changed files with 5956 additions and 1213 deletions

14
.github/ISSUE_TEMPLATE/aa-question.md vendored Normal file
View File

@@ -0,0 +1,14 @@
---
name: Question about using GAM
about: Help with using GAM or running it for the first time
title: Please use the GAM discussion group
labels: invalid
assignees: ''
---
If you need help with GAM, please do not file an issue here, it will be closed and ignored.
Please post your question to the GAM discussion group where other admins are ready and willing to help:
https://groups.google.com/g/google-apps-manager

23
.github/ISSUE_TEMPLATE/za-bug-report.md vendored Normal file
View File

@@ -0,0 +1,23 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: jay0lee
---
The issue tracker is for reporting product deficiencies. "How do I?" questions should be posted to the discussion forum at https://groups.google.com/group/google-apps-manager. When in doubt, start at the discussion forum and return here only when instructed to do so.
Please confirm the following:
* I have upgraded to the latest GAM release from https://git.io/gamreleases and I still have this issue.
* I am typing the command as described in the GAM Wiki at https://github.com/jay0lee/gam/wiki
Full steps to reproduce the issue:
1.
2.
3.
Expected outcome (what are you trying to do?):
Actual outcome (what errors or bad behavior do you see instead?):

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for GAM
title: ''
labels: enhancement
assignees: jay0lee
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

BIN
.github/actions/creds.tar.gpg vendored Normal file

Binary file not shown.

17
.github/actions/decrypt.sh vendored Normal file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
gpgfile="$1"
echo "source file is ${gpgfile}"
credsfile="$2"
echo "target file is ${credsfile}"
if [ -z ${PASSCODE+x} ]; then
echo "PASSCODE is unset";
else
echo "PASSCODE is set";
fi
gpg --quiet --batch --yes --decrypt --passphrase="${PASSCODE}" \
--output "${credsfile}" "${gpgfile}"
tar xvvf "${credsfile}" --directory "${gampath}"
ls -l "${gampath}"

View File

@@ -1,3 +1,6 @@
echo "RUNNING: apt update..."
sudo apt-get -qq --yes update > /dev/null
sudo apt-get -qq --yes install swig libpcsclite-dev
if [[ "$TRAVIS_JOB_NAME" == *"Testing" ]]; then
export python="python"
export pip="pip"
@@ -5,7 +8,7 @@ if [[ "$TRAVIS_JOB_NAME" == *"Testing" ]]; then
echo "running tests with this version"
else
export whereibelong=$(pwd)
echo "We are running on Ubuntu $TRAVIS_DIST $PLATFORM"
echo "We are running on $ImageOS $ImageVersion"
export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
cpucount=$(nproc --all)
echo "This device has $cpucount CPUs for compiling..."
@@ -32,8 +35,6 @@ else
rm -rf python
mkdir ssl
mkdir python
echo "RUNNING: apt update..."
sudo apt-get -qq --yes update > /dev/null
echo "RUNNING: apt upgrade..."
sudo apt-mark hold openssh-server
sudo apt-get --yes upgrade
@@ -93,7 +94,7 @@ else
python=~/python/bin/python3
pip=~/python/bin/pip3
if ([ "${TRAVIS_DIST}" == "trusty" ] || [ "${TRAVIS_DIST}" == "xenial" ]) && [ "${PLATFORM}" == "x86_64" ]; then
if ([ "${ImageOS}" == "ubuntu16" ]) && [ "${HOSTTYPE}" == "x86_64" ]; then
echo "Installing deps for StaticX..."
if [ ! -d patchelf-$PATCHELF_VERSION ]; then
echo "Downloading PatchELF $PATCHELF_VERSION"
@@ -108,11 +109,5 @@ else
$pip install staticx
fi
$pip install --upgrade git+git://github.com/pyinstaller/pyinstaller.git@$PYINSTALLER_COMMIT
cd $whereibelong
fi
echo "Upgrading pip packages..."
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
$pip install --upgrade -r src/requirements.txt

34
.github/actions/linux-install.sh vendored Executable file
View File

@@ -0,0 +1,34 @@
export distpath="dist/"
export gampath="${distpath}gam"
rm -rf $gampath
#mkdir -p $gampath
#export gampath=$(readlink -e $gampath)
$pip install wheel
$python -OO -m PyInstaller --clean --noupx --strip --distpath $gampath gam.spec
export gam="${gampath}/gam"
export GAMVERSION=`$gam version simple`
cp LICENSE $gampath
cp GamCommands.txt $gampath
this_glibc_ver=$(ldd --version | awk '/ldd/{print $NF}')
GAM_ARCHIVE="gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-glibc${this_glibc_ver}.tar.xz"
rm $gampath/lastupdatecheck.txt
# tar will cd to dist and tar up gam/
tar -C ${distpath} --create --file $GAM_ARCHIVE --xz gam
echo "PyInstaller GAM info:"
du -h $gam
time $gam version extended
if ([ "${ImageOS}" == "ubuntu16" ]) && [ "${HOSTTYPE}" == "x86_64" ]; then
GAM_LEGACY_ARCHIVE=gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-legacy.tar.xz
$python -OO -m staticx -l /lib/x86_64-linux-gnu/libresolv.so.2 -l /lib/x86_64-linux-gnu/libnss_dns.so.2 $gam $gam-staticx
strip $gam-staticx
rm $gampath/gam
mv $gam-staticx $gam
chmod 755 $gam
rm $gampath/lastupdatecheck.txt
tar -C dist/ --create --file $GAM_LEGACY_ARCHIVE --xz gam
echo "Legacy StaticX GAM info:"
du -h $gam
time $gam version extended
fi
echo "GAM packages:"
ls -l gam-*.tar.xz

136
.github/actions/macos-before-install.sh vendored Executable file
View File

@@ -0,0 +1,136 @@
mypath=$HOME
whereibelong=$(pwd)
cpucount=$(sysctl -n hw.ncpu)
echo "This device has $cpucount CPUs for compiling..."
#echo "Brew installing xz..."
#brew install xz > /dev/null
#brew upgrade
brew install coreutils
brew install bash
# prefer standard GNU tools like date over MacOS defaults
export PATH="/usr/local/opt/coreutils/libexec/gnubin:$(brew --prefix)/opt/gnu-tar/libexec/gnubin:$PATH"
date --version
gdate --version
bash --version
cd ~
# Use official Python.org version of Python which is backwards compatible
# with older MacOS versions
if [ "$PLATFORM" == "x86_64" ]; then
export pyfile=python-$BUILD_PYTHON_VERSION-macosx10.9.pkg
else
export pyfile=python-$BUILD_PYTHON_VERSION-macos11.pkg
fi
wget https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/$pyfile
echo "installing Python $BUILD_PYTHON_VERSION..."
sudo installer -pkg ./$pyfile -target /
# This fixes https://github.com/pyinstaller/pyinstaller/issues/5062
codesign --remove-signature /Library/Frameworks/Python.framework/Versions/3.9/Python
#if [ ! -f python-$MIN_PYTHON_VERSION-macosx10.9.pkg ]; then
# wget --quiet https://www.python.org/ftp/python/$MIN_PYTHON_VERSION/python-$MIN_PYTHON_VERSION-macosx10.9.pkg
#fi
#sudo installer -pkg python-$MIN_PYTHON_VERSION-macosx10.9.pkg -target /
#brew install openssl@1.1
#brew upgrade python
#export python=python3
#export pip=pip3
#echo "Python location:"
#which $python
cd ~
#export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
#export openssl=~/ssl/bin/openssl
#export python=~/python/bin/python3
#export pip=~/python/bin/pip3
export python=/usr/local/bin/python3
export pip=/usr/local/bin/pip3
SSLVER=$($openssl version)
SSLRESULT=$?
PYVER=$($python -V)
PYRESULT=$?
brew install swig
$pip install pyscard
#wget --quiet https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/python-$BUILD_PYTHON_VERSION-macosx10.9.pkg
#if [ $SSLRESULT -ne 0 ] || [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]] || [ $PYRESULT -ne 0 ] || [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION"* ]]; then
# echo "SSL Result: $SSLRESULT - SSL Ver: $SSLVER - Py Result: $PYRESULT - Py Ver: $PYVER"
# if [ $SSLRESULT -ne 0 ]; then
# echo "sslresult -ne 0"
# fi
# if [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]]; then
# echo "sslver not equal to..."
# fi
# if [ $PYRESULT -ne 0 ]; then
# echo "pyresult -ne 0"
# fi
# if [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION" ]]; then
# echo "pyver not equal to..."
# fi
# Start clean
# rm -rf python
# rm -rf ssl
# mkdir python
# mkdir ssl
# Compile latest OpenSSL
# wget --quiet https://www.openssl.org/source/openssl-$BUILD_OPENSSL_VERSION.tar.gz
# echo "Extracting OpenSSL..."
# tar xf openssl-$BUILD_OPENSSL_VERSION.tar.gz
# cd openssl-$BUILD_OPENSSL_VERSION
# echo "Compiling OpenSSL $BUILD_OPENSSL_VERSION..."
# ./config shared --prefix=$HOME/ssl
# echo "Running make for OpenSSL..."
# make -j$cpucount -s
# echo "Running make install for OpenSSL..."
# make install > /dev/null
# cd ~
# Compile latest Python
# echo "Downloading Python $BUILD_PYTHON_VERSION..."
# curl -O https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/Python-$BUILD_PYTHON_VERSION.tar.xz
# echo "Extracting Python..."
# tar xf Python-$BUILD_PYTHON_VERSION.tar.xz
# cd Python-$BUILD_PYTHON_VERSION
# echo "Compiling Python $BUILD_PYTHON_VERSION..."
# safe_flags="--with-openssl=$HOME/ssl --enable-shared --prefix=$HOME/python --with-ensurepip=upgrade"
# unsafe_flags="--enable-optimizations --with-lto"
# if [ ! -e Makefile ]; then
# echo "running configure with safe and unsafe"
# ./configure $safe_flags $unsafe_flags > /dev/null
# fi
# make -j$cpucount PROFILE_TASK="-m test.regrtest --pgo -j$(( $cpucount * 2 ))" -s
# RESULT=$?
# echo "First make exited with $RESULT"
# if [ $RESULT != 0 ]; then
# echo "Trying Python compile again without unsafe flags..."
# make clean
# ./configure $safe_flags > /dev/null
# make -j$cpucount -s
# echo "Sticking with safe Python for now..."
# fi
# echo "Installing Python..."
# make install > /dev/null
# cd ~
#fi
$python -V
cd $whereibelong

19
.github/actions/macos-install.sh vendored Executable file
View File

@@ -0,0 +1,19 @@
echo "MacOS Version Info According to Python:"
macver=$(python -c "import platform; print(platform.mac_ver()[0])")
echo $macver
echo "Xcode version:"
xcodebuild -version
export distpath="dist/"
export gampath="${distpath}gam"
rm -rf $gampath
export specfile="gam.spec"
$python -OO -m PyInstaller --clean --noupx --strip --distpath "${gampath}" --target-architecture $PLATFORM "${specfile}"
export gam="${gampath}/gam"
$gam version extended
export GAMVERSION=`$gam version simple`
cp LICENSE "${gampath}"
cp GamCommands.txt "${gampath}"
GAM_ARCHIVE="gam-${GAMVERSION}-${GAMOS}-${PLATFORM}.tar.xz"
rm "${gampath}/lastupdatecheck.txt"
# tar will cd to dist/ and tar up gam/
tar -C dist/ --create --file $GAM_ARCHIVE --xz gam

57
.github/actions/windows-before-install.sh vendored Executable file
View File

@@ -0,0 +1,57 @@
if [[ "$PLATFORM" == "x86_64" ]]; then
export BITS="64"
export PYTHONFILE_BITS="-amd64"
export OPENSSL_BITS="-x64"
elif [[ "$PLATFORM" == "x86" ]]; then
export BITS="32"
export PYTHONFILE_BITS=""
export OPENSSL_BITS=""
export CHOCOPTIONS="--forcex86"
fi
echo "This is a ${BITS}-bit build for ${PLATFORM}"
export mypath=$(pwd)
cd ~
export python="python"
export pip="pip"
# pyscard needs swig, keep these two together
choco install $CHOCOPTIONS swig
$pip install pyscard
# Python
#echo "Installing Python..."
#export python_file=python-${BUILD_PYTHON_VERSION}${PYTHONFILE_BITS}.exe
#if [ ! -e $python_file ]; then
# echo "Downloading $python_file..."
# curl -O https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/$python_file
#fi
#until ./${python_file} /quiet InstallAllUsers=1 TargetDir=c:\\python; do echo "trying python again..."; done
#export python=/c/python/python.exe
#export pip=/c/python/scripts/pip.exe
#until [ -f $python ]; do sleep 1; done
#export PATH=$PATH:/c/python/scripts
# OpenSSL
#echo "Installing OpenSSL..."
#export exefile=Win${BITS}OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
#if [ ! -e $exefile ]; then
# echo "Downloading $exefile..."
# curl -O https://slproweb.com/download/$exefile
#fi
#until ./${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl; do echo "trying openssl again..."; done
#until cp -v /c/ssl/libcrypto-1_1${OPENSSL_BITS}.dll /c/python/DLLs/; do echo "trying libcrypto copy again..."; sleep 3; done
#until cp -v /c/ssl/libssl-1_1${OPENSSL_BITS}.dll /c/python/DLLs/; do echo "trying libssl copy again..."; done
#if [[ "$PLATFORM" == "x86_64" ]]; then
# cp -v /c/python/DLLs/libssl-1_1-x64.dll /c/python/DLLs/libssl-1_1.dll
# cp -v /c/python/DLLs/libcrypto-1_1-x64.dll /c/python/DLLs/libcrypto-1_1.dll
#fi
cd $mypath
echo "PATH: $PATH"
cd ..
$python setup.py install
echo "cd to $mypath"
cd $mypath

View File

@@ -1,21 +1,29 @@
cd src
if [[ "$PLATFORM" == "x86_64" ]]; then
export WIX_BITS="x64"
elif [[ "$PLATFORM" == "x86" ]]; then
export WIX_BITS="x86"
fi
echo "compiling GAM with pyinstaller..."
export gampath="dist/gam"
export distpath="dist/"
export gampath="${distpath}gam"
rm -rf $gampath
mkdir -p $gampath
export gampath=$(readlink -e $gampath)
pyinstaller --clean --noupx -F --distpath $gampath gam.spec
#mkdir -p $gampath
#export gampath=$(readlink -e $gampath)
pyinstaller --clean --noupx --distpath $gampath gam.spec
export gam="${gampath}/gam"
echo "running compiled GAM..."
$gam version
export GAMVERSION=`$gam version simple`
export GAMVERSION=$($gam version simple)
rm $gampath/lastupdatecheck.txt
cp LICENSE $gampath
cp GamCommands.txt $gampath
cp gam-setup.bat $gampath
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM.zip
/c/Program\ Files/7-Zip/7z.exe a -tzip $GAM_ARCHIVE $gampath -xr!.svn
cwd=$(pwd)
cd "${distpath}"
/c/Program\ Files/7-Zip/7z.exe a -tzip $GAM_ARCHIVE gam -xr!.svn
mv "${GAM_ARCHIVE}" "${cwd}"
cd "${cwd}"
echo "Running WIX candle $WIX_BITS..."
/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.11/bin/candle.exe -arch $WIX_BITS gam.wxs
echo "Done with WIX candle..."

355
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,355 @@
name: Build and test GAM
on:
push:
pull_request:
schedule:
- cron: '37 22 * * *'
defaults:
run:
shell: bash
working-directory: src
env:
BUILD_PYTHON_VERSION: "3.9.5"
MIN_PYTHON_VERSION: "3.9.5"
BUILD_OPENSSL_VERSION: "1.1.1k"
MIN_OPENSSL_VERSION: "1.1.1k"
PATCHELF_VERSION: "0.12"
# PYINSTALLER_VERSION can be full commit hash or version like v4.20
PYINSTALLER_VERSION: "e5dbb051bd3d53d6c2c70cbd87270eec1765da2e"
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: ubuntu-16.04
jid: 1
goal: "build"
gamos: "linux"
platform: "x86_64"
- os: ubuntu-18.04
jid: 2
goal: "build"
gamos: "linux"
platform: "x86_64"
- os: ubuntu-20.04
jid: 3
goal: "build"
gamos: "linux"
platform: "x86_64"
- os: macos-10.15
jid: 4
goal: "build"
gamos: "macos"
platform: "x86_64"
- os: macos-11.0
jid: 12
goal: "build"
gamos: "macos"
platform: "universal2"
- os: windows-2019
jid: 5
goal: "build"
gamos: "windows"
python: 3.9.5
pyarch: "x64"
platform: "x86_64"
- os: windows-2019
jid: 6
goal: "build"
gamos: "windows"
platform: "x86"
python: 3.9.5
pyarch: "x86"
- os: ubuntu-20.04
goal: "test"
python: "3.6"
jid: 7
gamos: "linux"
platform: "x86_64"
- os: ubuntu-20.04
goal: "test"
python: "3.7"
jid: 8
gamos: "linux"
platform: "x86_64"
- os: ubuntu-20.04
goal: "test"
python: "3.8"
jid: 9
gamos: "linux"
platform: "x86_64"
- os: ubuntu-20.04
goal: test
python: "3.10.0-beta.1"
jid: 10
gamos: linux
platform: x86_64
steps:
- uses: actions/checkout@master
with:
persist-credentials: false
fetch-depth: 0
- name: Cache multiple paths
uses: actions/cache@v2
if: matrix.goal != 'test'
with:
path: |
~/python
~/ssl
key: ${{ matrix.os }}-${{ matrix.jid }}-20210611
- name: Set env variables
env:
GAMOS: ${{ matrix.gamos }}
GOAL: ${{ matrix.goal }}
JID: ${{ matrix.jid }}
PLATFORM: ${{ matrix.platform }}
run: |
echo "GAMOS=${GAMOS}" >> $GITHUB_ENV
echo "GOAL=${GOAL}" >> $GITHUB_ENV
echo "JID=${JID}" >> $GITHUB_ENV
echo "PLATFORM=${PLATFORM}" >> $GITHUB_ENV
uname -a
- name: Use pre-compiled Python for testing and Windows
if: matrix.python != ''
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
architecture: ${{ matrix.pyarch }}
- name: Set env variables for pre-compiled Python
if: matrix.goal == 'test'
run: |
export python=$(which python3)
export pip=$(which pip3)
export gam="${python} -m gam"
export gampath="$(readlink -e .)"
echo -e "python: $python\npip: $pip\ngam: $gam\ngampath: $gampath"
echo "python=${python}" >> $GITHUB_ENV
echo "pip=${pip}" >> $GITHUB_ENV
echo "gam=${gam}" >> $GITHUB_ENV
echo "gampath=${gampath}" >> $GITHUB_ENV
echo "RUNNING: apt update..."
sudo apt-get -qq --yes update > /dev/null
sudo apt-get -qq --yes install swig libpcsclite-dev
$pip install --upgrade pip
- name: Build and install Python, OpenSSL and PyInstaller
if: matrix.goal != 'test' && steps.cache-primes.outputs.cache-hit != 'true'
run: |
set +e
source ../.github/actions/${GAMOS}-before-install.sh
echo "PATH=$PATH" >> $GITHUB_ENV # keep gnutools for MacOS
echo "python=$python" >> $GITHUB_ENV
echo "pip=$pip" >> $GITHUB_ENV
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> $GITHUB_ENV
echo -e "Python: $python\nPip: $pip\nLD_LIB...: $LD_LIBRARY_PATH"
$pip install wheel
export url="https://codeload.github.com/pyinstaller/pyinstaller/tar.gz/${PYINSTALLER_VERSION}"
echo "Downloading ${url}"
curl -o pyinstaller.tar.gz --compressed "${url}"
tar xf pyinstaller.tar.gz
cd "pyinstaller-${PYINSTALLER_VERSION}/bootloader"
if [ "${PLATFORM}" == "x86" ]; then
TARGETARCH="--target-arch=32bit"
else
TARGETARCH=""
fi
#$python ./waf all $TARGETARCH
cd ..
$python setup.py install
#$pip install pyinstaller
- name: Install pip requirements
if: matrix.os != 'self-hosted'
run: |
set +e
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U --force-reinstall
$pip install --upgrade -r requirements.txt
- name: Build GAM with PyInstaller
if: matrix.goal != 'test'
run: |
set +e
source ../.github/actions/${GAMOS}-install.sh
ls -alRF $gampath
echo "gampath=$gampath" >> $GITHUB_ENV
echo "gam=$gam" >> $GITHUB_ENV
echo -e "GAM: ${gam}\nGAMPATH: ${gampath}\nGAMVERSION: ${GAMVERSION}"
- name: Basic Tests all jobs
run: |
echo -e "python: $python\npip: $pip\ngam: $gam\ngampath: $gampath\n"
$python -m unittest discover --start-directory ./ --pattern "*_test.py" --buffer
touch "${gampath}/nobrowser.txt"
$gam version extended
export GAMVERSION=$($gam version simple)
echo "GAM Version ${GAMVERSION}"
echo "GAMVERSION=${GAMVERSION}" >> $GITHUB_ENV
- name: Basic Tests build jobs only
if: matrix.goal != 'test'
run: |
export vline=$($gam version | grep "Python ")
export python_line=($vline)
export this_python=${python_line[1]}
$python tools/a_atleast_b.py "${this_python}" "${MIN_PYTHON_VERSION}"
export vline=$($gam version extended | grep "OpenSSL ")
export openssl_line=($vline)
export this_openssl="${openssl_line[1]}"
$python tools/a_atleast_b.py "${this_openssl}" "${MIN_OPENSSL_VERSION}"
- name: Live API tests push only
if: github.event_name == 'push' || github.event_name == 'schedule'
env: # Or as an environment variable
PASSCODE: ${{ secrets.PASSCODE }}
run: |
source ../.github/actions/decrypt.sh ../.github/actions/creds.tar.gpg creds.tar
export OAUTHFILE="oauth2.txt-gam-gha-${JID}"
echo "OAUTHFILE=${OAUTHFILE}" >> $GITHUB_ENV
export gam_user="gam-gha-${JID}@pdl.jaylee.us"
echo "gam_user=${gam_user}" >> $GITHUB_ENV
$gam oauth info
$gam info domain
$gam oauth refresh
$gam info user
#$gam info user $gam_user grouptree
export tstamp=$(date +%s%3N)
export newbase=gha-test-$JID-$tstamp
export newuser=$newbase@pdl.jaylee.us
export newgroup=$newbase-group@pdl.jaylee.us
export newalias=$newbase-alias@pdl.jaylee.us
export newbuilding=$newbase-building
export newresource=$newbase-resource
export GAM_THREADS=5
echo email > sample.csv;
for i in {01..10}; do
echo "${newbase}-bulkuser-$i" >> sample.csv;
done
$gam create user $newuser firstname GHA lastname $JID password random recoveryphone 12125121110 recoveryemail jay0lee@gmail.com gha.jid $JID
$gam user $gam_user sendemail recipient $newuser subject "test message $newbase" message "GHA test message"
$gam user $gam_user sendemail recipient exchange@pdl.jaylee.us subject "test ${tstamp}" message "test message"
$gam create group $newgroup name "GHA $JID group" description "This is a description" isarchived true
$gam user $newuser add license gsuitebusiness
$gam update group $newgroup add owner $gam_user
$gam update group $newgroup add member $newuser
$gam csv sample.csv gam create user ~~email~~ firstname "GHA Bulk" lastname ~~email~~ gha.jid $JID
$gam csv sample.csv gam update user ~~email~~ recoveryphone 12125121110 recoveryemail jay0lee@gmail.com password random
$gam csv sample.csv gam update user ~~email~~ recoveryphone "" recoveryemail ""
$gam csv sample.csv gam user ~email add license gsuitebusiness
$gam csv sample.csv gam user $gam_user sendemail recipient ~~email~~@pdl.jaylee.us subject "test message $newbase" message "GHA test message"
$gam csv sample.csv gam update group $newgroup add member ~email
$gam info group $newgroup
$gam info cigroup $newgroup membertree
$gam user $gam_user check serviceaccount
# confirm mailbox is provisoned before continuing
$gam user $newuser waitformailbox
$gam user $newuser imap on
$gam user $newuser show imap
$gam user $newuser show delegates
#$gam user $newuser add contactdelegate "${newbase}-bulkuser-01"
#$gam user $newuser print contactdelegates
export biohazard=$(echo -e '\xe2\x98\xa3')
$gam user $newuser label "$biohazard unicode biohazard $biohazard"
$gam user $newuser show labels
$gam user $newuser show labels > labels.txt
$gam user $gam_user importemail subject "GHA import $newbase" message "This is a test import" labels IMPORTANT,UNREAD,INBOX,STARRED
$gam user $gam_user insertemail subject "GHA insert $newbase" file gam.py labels INBOX,UNREAD # yep body is gam code
$gam user $gam_user sendemail subject "GHA send $gam_user $newbase" file gam.py recipient admin@pdl.jaylee.us
$gam user $gam_user draftemail subject "GHA draft $newbase" message "Draft message test"
$gam csvfile sample.csv:email waitformailbox
$gam user $newuser delegate to "${newbase}-bulkuser-01"
$gam users "$gam_user $newbase-bulkuser-01 $newbase-bulkuser-02 $newbase-bulkuser-03" delete messages query in:anywhere maxtodelete 99999 doit
$gam users "$newbase-bulkuser-04 $newbase-bulkuser-05 $newbase-bulkuser-06" trash messages query in:anywhere maxtotrash 99999 doit
$gam users "$newbase-bulkuser-07 $newbase-bulkuser-08 $newbase-bulkuser-09" modify messages query in:anywhere maxtomodify 99999 addlabel IMPORTANT addlabel STARRED doit
$gam user $newuser delete label --ALL_LABELS--
$gam create feature name Whiteboard-$newbase
$gam create feature name VC-$newbase
$gam create building "My Building - $newbase" id $newbuilding floors 1,2,3,4,5,6,7,8,9,10,11,12,14,15 description "No 13th floor here..."
$gam create resource $newresource "Resource Calendar $tstamp" capacity 25 features Whiteboard-$newbase,VC-$newbase building $newbuilding floor 15 type Room
$gam info resource $newresource
$gam user $newuser show filelist
$gam calendar $gam_user printacl | $gam csv - gam calendar $gam_user delete id ~id # clear ACLs
$gam calendar $gam_user update read domain
$gam calendar $gam_user update freebusy default
$gam calendar $gam_user add editor $newuser
$gam calendar $gam_user showacl
$gam calendar $gam_user printacl | $gam csv - gam calendar $gam_user delete id ~id
$gam calendar $gam_user addevent summary "GHA test event" start $(date '+%FT%T.%N%:z' -d "now + 1 hour") end $(date '+%FT%T.%N%:z' -d "now + 2 hours") attendee $newgroup hangoutsmeet guestscanmodify true sendupdates all
$gam calendar $gam_user printevents after -0d
matterid=uid:$($gam create vaultmatter name "GHA matter $newbase" description "test matter" collaborators $newuser | head -1 | cut -d ' ' -f 3)
$gam create vaulthold matter $matterid name "GHA hold $newbase" corpus mail accounts $newuser
$gam print vaultmatters matterstate open
$gam print vaultholds matter $matterid
$gam print vaultcount matter $matterid corpus mail everyone todrive
$gam create vaultexport matter $matterid name "GHA export $newbase" corpus mail accounts $newuser
$gam print exports matter $matterid | $gam csv - gam info export $matterid id:~~id~~
$gam csv sample.csv gam user ~email add calendar id:$newresource
$gam delete resource $newresource
$gam delete feature Whiteboard-$newbase
$gam delete feature VC-$newbase
$gam delete building $newbuilding
$gam delete group $newgroup
$gam create alias $newalias user $newuser
$gam whatis $newuser
$gam user $gam_user show tokens
$gam print exports matter $matterid | $gam csv - gam download export $matterid id:~~id~~
$gam delete hold "GHA hold $newbase" matter $matterid
$gam update matter $matterid action close
$gam update matter $matterid action delete
$gam delete user $newuser
$gam print users query "gha.jid=$JID" | $gam csv - gam delete user ~primaryEmail
$gam print mobile
$gam print devices
$gam print browsers
export sn="$JID$JID$JID$JID-$(openssl rand -base64 32 | sed 's/[^a-zA-Z0-9]//g')"
$gam create device serialnumber $sn devicetype android
$gam print cros allfields nolists
$gam report usageparameters customer
$gam report usage customer parameters gmail:num_emails_sent,accounts:num_1day_logins
$gam report customer todrive
$gam report users fields accounts:is_less_secure_apps_access_allowed,gmail:last_imap_time,gmail:last_pop_time filters "accounts:last_login_time>2019-01-01T00:00:00.000Z" todrive
$gam report admin start -3d todrive
$gam print devices nopersonaldevices nodeviceusers filter "serial:$JID$JID$JID$JID-" | $gam csv - gam delete device id ~name
$gam print userinvitations
$gam print userinvitations | $gam csv - gam send userinvitation ~name
export CUSTOMER_ID="C01wfv983"
export GA_DOMAIN="pdl.jaylee.us"
touch $gampath/enabledasa.txt
echo "printer model count:"
$gam print printermodels | wc -l
#$gam print printers
#$gam create printer displayname "${newbase}" uri ipp://localhost:631 driverless description "made by $(date)"
rm $gampath/enabledasa.txt
- name: Upload to Google Drive, build only.
if: github.event_name == 'push' && matrix.goal != 'test'
run: |
ls gam-$GAMVERSION-*
for gamfile in gam-$GAMVERSION-*; do
echo "Uploading file ${gamfile} to Google Drive..."
fileid=$($gam user $gam_user add drivefile localfile $gamfile drivefilename $GAMVERSION-${GITHUB_SHA:0:7}-$gamfile parentid 1N2zbO33qzUQFsGM49-m9AQC1ijzd_ru1 returnidonly)
echo "file uploaded as ${fileid}, setting ACL..."
$gam user $gam_user add drivefileacl $fileid anyone role reader withlink
done
- name: Archive production artifacts
uses: actions/upload-artifact@v2
if: github.event_name == 'push' && matrix.goal != 'test'
with:
name: gam-binaries
path: |
src/*.tar.xz
src/*.zip
src/*.msi

View File

@@ -1,253 +0,0 @@
if: tag IS blank
os: linux
language: python
dist: focal
env:
global:
- BUILD_PYTHON_VERSION=3.9.0
- MIN_PYTHON_VERSION=3.9.0
- BUILD_OPENSSL_VERSION=1.1.1h
- MIN_OPENSSL_VERSION=1.1.1h
- PATCHELF_VERSION=0.11
- PYINSTALLER_COMMIT=7aa19839c171d898b5cf957739083c4bb901607e
cache:
directories:
- $HOME/.cache/pip
- $HOME/python
- $HOME/ssl
jobs:
allow_failures:
- python: nightly
fast_finish: true
include:
- os: linux
name: "Linux 64-bit Focal"
dist: focal
language: shell
- os: linux
name: "Linux 64-bit Bionic"
dist: bionic
language: shell
- os: linux
name: "Linux 64-bit Xenial"
dist: xenial
language: shell
- os: linux
name: "Linux ARM64 Focal"
dist: focal
language: shell
arch: arm64
filter_secrets: false
- os: linux
dist: bionic
arch: arm64
name: "Linux ARM64 Bionic"
language: shell
filter_secrets: false
- os: linux
dist: xenial
arch: arm64
name: "Linux ARM64 Xenial"
language: shell
filter_secrets: false
- os: linux
name: "Python 3.6 Source Testing"
language: python
python: 3.6
- os: linux
name: "Python 3.7 Source Testing"
language: python
python: 3.7
- os: linux
name: "Python 3.8 Source Testing"
language: python
python: 3.8
- os: linux
name: "Python 3.10 dev Source Testing"
language: python
python: 3.10-dev
# - os: linux
# name: "Python trunk nightly Source Testing"
# language: python
# python: nightly
# - os: linux
# name: "Python PyPi Source Testing"
# language: python
# python: pypy3
- os: osx
name: "MacOS 10.13"
language: generic
osx_image: xcode10.1
- os: osx
name: "MacOS 10.14"
language: generic
osx_image: xcode11.3
- os: osx
name: "MacOS 10.15"
language: generic
osx_image: xcode11.7
- os: osx
name: "MacOS 10.15 Universal Testing"
language: generic
osx_image: xcode12u
- os: windows
name: "Windows 64-bit"
language: shell
- os: windows
name: "Windows 32-bit"
language: shell
before_install:
- if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
export GAMOS="macos";
else
export GAMOS="${TRAVIS_OS_NAME}";
fi
- if [ "${TRAVIS_JOB_NAME}" == "Windows 32-bit" ]; then
export PLATFORM="x86";
elif [ "${TRAVIS_CPU_ARCH}" == "amd64" ]; then
export PLATFORM="x86_64";
else
export PLATFORM="${TRAVIS_CPU_ARCH}";
fi
- source src/travis/${TRAVIS_OS_NAME}-before-install.sh
install:
- source src/travis/${TRAVIS_OS_NAME}-install.sh
script:
# Discover and run all Python unit tests. Buffer output so that it's not sent to the build log.
- $python -m unittest discover --start-directory ./ --pattern "*_test.py" --buffer
- touch $gampath/nobrowser.txt
- $gam version extended
- $gam version | grep travis # travis should be part of the path (not /tmp or such)
# determine which Python version GAM is built with and ensure it's at least build version from above.
- if [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]; then vline=$($gam version | grep "Python "); python_line=($vline); this_python=${python_line[1]}; $python tools/a_atleast_b.py $this_python $MIN_PYTHON_VERSION; fi
# determine which OpenSSL version GAM is built with and ensure it's at least build version from above.
- if [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]; then vline=$($gam version extended | grep "OpenSSL "); openssl_line=($vline); this_openssl=${openssl_line[1]}; $python tools/a_atleast_b.py $this_openssl $MIN_OPENSSL_VERSION; fi
- if [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]; then $gam version extended | grep TLSv1\.[23]; fi # Builds should default TLS 1.2 or 1.3 to Google
- if [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]; 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
- if [ "$e2e" = true ]; then openssl aes-256-cbc -K $encrypted_6294a53f809d_key -iv $encrypted_6294a53f809d_iv -in travis/creds.tar.enc -out travis/creds.tar -d; fi
- if [ "$e2e" = true ]; then tar xvf travis/creds.tar -C $gampath; fi
- if [ "$e2e" = true ]; then export OAUTHFILE=oauth2.txt-gam-travis-$jid; fi
- if [ "$e2e" = true ]; then $gam oauth info; fi
- if [ "$e2e" = true ]; then $gam info domain; fi
- if [ "$e2e" = true ]; then $gam oauth refresh; fi
- if [ "$e2e" = true ]; then $gam info user; fi
- if [ "$e2e" = true ]; then export tstamp=$(date +%s%3N);
export newbase=travis-test-$jid-$tstamp;
export newuser=$newbase@pdl.jaylee.us;
export newgroup=$newbase-group@pdl.jaylee.us;
export newalias=$newbase-alias@pdl.jaylee.us;
export newbuilding=$newbase-building;
export newresource=$newbase-resource;
export GAM_THREADS=5; fi
- if [ "$e2e" = true ]; then echo email > sample.csv;
for i in {01..20};
do echo $newbase-bulkuser-$i >> sample.csv;
done; fi
- if [ "$e2e" = true ]; then $gam create user $newuser firstname Travis lastname $jid password random recoveryphone 12125121110 recoveryemail jay0lee@gmail.com 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 user $gam_user sendemail recipient exchange@pdl.jaylee.us subject "test ${tstamp}" message "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 update user ~~email~~ recoveryphone 12125121110 recoveryemail jay0lee@gmail.com password random; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update user ~~email~~ recoveryphone "" recoveryemail ""; 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
- if [ "$e2e" = true ]; then $gam user $newuser imap on; fi
- 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 [ "$e2d" = true ]; then export biohazard=$(echo -e '\xe2\x98\xa3'); fi
- if [ "$e2e" = true ]; then $gam user $newuser label "$biohazard unicode biohazard $biohazard"; fi
- if [ "$e2e" = true ]; then $gam user $newuser show labels; fi
- if [ "$e2e" = true ]; then $gam user $newuser show labels > labels.txt; 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 user $newuser delete label --ALL_LABELS--; fi
- if [ "$e2e" = true ]; then $gam create feature name Whiteboard-$newbase; fi
- if [ "$e2e" = true ]; then $gam create feature name VC-$newbase; fi
- if [ "$e2e" = true ]; then $gam create building "My Building - $newbase" id $newbuilding floors 1,2,3,4,5,6,7,8,9,10,11,12,14,15 description "No 13th floor here..."; fi
- if [ "$e2e" = true ]; then $gam create resource $newresource "Resource Calendar $tstamp" capacity 25 features Whiteboard-$newbase,VC-$newbase building $newbuilding floor 15 type Room; fi
- if [ "$e2e" = true ]; then $gam info resource $newresource; fi
- if [ "$e2e" = true ]; then $gam user $newuser show filelist; fi
- if [ "$e2e" = true ]; then $gam calendar $gam_user printacl | $gam csv - gam calendar $gam_user delete id ~id; fi # clear ACLs
- if [ "$e2e" = true ]; then $gam calendar $gam_user update read domain; fi
- if [ "$e2e" = true ]; then $gam calendar $gam_user update freebusy default; fi
- if [ "$e2e" = true ]; then $gam calendar $gam_user add editor $newuser; fi
- if [ "$e2e" = true ]; then $gam calendar $gam_user showacl; fi
- if [ "$e2e" = true ]; then $gam calendar $gam_user printacl | $gam csv - gam calendar $gam_user delete id ~id; fi
- if [ "$e2e" = true ]; then $gam calendar $gam_user addevent summary "Travis test event" start $(date '+%FT%T.%N%:z' -d "now + 1 hour") end $(date '+%FT%T.%N%:z' -d "now + 2 hours") attendee $newgroup hangoutsmeet guestscanmodify true sendupdates all; fi
- if [ "$e2e" = true ]; then $gam calendar $gam_user printevents after -0d; fi
- if [ "$e2e" = true ]; then matterid=uid:$($gam create vaultmatter name "Travis matter $newbase" description "test matter" collaborators $newuser | head -1 | cut -d ' ' -f 3); fi
- if [ "$e2e" = true ]; then $gam create vaulthold matter $matterid name "Travis hold $newbase" corpus mail accounts $newuser; fi
- if [ "$e2e" = true ]; then $gam print vaultmatters matterstate open; fi
- if [ "$e2e" = true ]; then $gam print vaultholds matter $matterid; fi
- if [ "$e2e" = true ]; then $gam create vaultexport matter $matterid name "Travis export $newbase" corpus mail accounts $newuser; fi
- if [ "$e2e" = true ]; then $gam print exports matter $matterid | $gam csv - gam info export $matterid id:~~id~~; 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
- if [ "$e2e" = true ]; then $gam delete feature VC-$newbase; fi
- if [ "$e2e" = true ]; then $gam delete building $newbuilding; fi
- if [ "$e2e" = true ]; then $gam delete group $newgroup; fi
- if [ "$e2e" = true ]; then $gam create alias $newalias user $newuser; fi
- if [ "$e2e" = true ]; then $gam whatis $newuser; fi
- if [ "$e2e" = true ]; then $gam user $gam_user show tokens; fi
- if [ "$e2e" = true ]; then $gam print exports matter $matterid | $gam csv - gam download export $matterid id:~~id~~; fi
- if [ "$e2e" = true ]; then $gam delete hold "Travis hold $newbase" matter $matterid; fi
- if [ "$e2e" = true ]; then $gam update matter $matterid action close; fi
- if [ "$e2e" = true ]; then $gam update matter $matterid action delete; fi
- if [ "$e2e" = true ]; then $gam delete user $newuser; fi
- if [ "$e2e" = true ]; then $gam print users query "travis.jid=$jid" | $gam csv - gam delete user ~primaryEmail; fi
- if [ "$e2e" = true ]; then $gam print mobile; fi
- if [ "$e2e" = true ]; then $gam print devices; fi
- if [ "$e2e" = true ]; then export sn="$jid$jid$jid$jid$jid-$(openssl rand -base64 32 | sed 's/[^a-zA-Z0-9]//g')"; fi
- if [ "$e2e" = true ]; then $gam create device serialnumber $sn devicetype android; fi
- if [ "$e2e" = true ]; then $gam print cros allfields nolists; fi
- if [ "$e2e" = true ]; then $gam report usageparameters customer; fi
- if [ "$e2e" = true ]; then $gam report usage customer parameters gmail:num_emails_sent,accounts:num_1day_logins; fi
- if [ "$e2e" = true ]; then $gam report customer todrive; fi
- if [ "$e2e" = true ]; then $gam report users fields accounts:is_less_secure_apps_access_allowed,gmail:last_imap_time,gmail:last_pop_time filters "accounts:last_login_time>2019-01-01T00:00:00.000Z" todrive; fi
- if [ "$e2e" = true ]; then $gam report admin start -3d todrive; fi
- if [ "$e2e" = true ]; then $gam print devices nopersonaldevices nodeviceusers filter "serial:$jid$jid$jid$jid$jid-" | $gam csv - gam delete device id ~name; fi
- if ([ "$e2e" = true ] && [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]); then
for gamfile in gam-$GAMVERSION-*; do
fileid=$($gam user $gam_user add drivefile localfile $gamfile drivefilename $GAMVERSION-${TRAVIS_COMMIT:0:7}-$gamfile parentid 1N2zbO33qzUQFsGM49-m9AQC1ijzd_ru1 returnidonly);
$gam user $gam_user add drivefileacl $fileid anyone role reader withlink;
done;
fi
before_deploy:
- export TRAVIS_TAG="preview"
- unset LD_LIBRARY_PATH
deploy:
provider: releases
token:
secure: bzambMcQwyv/o5c5GrKGCsZHgE5R85tg8sNFvPfpISz3+uosCjnBXas7wvCKzT75XUFi2ztfbYak6HdKf4sGnNHk0saEicB3slH+ghPyZbYzp76yvvduhFO2nWW3/F01tL+Yfqqt4/q8wFaWGjrC5km+6GLVyB4lWA/Uyu49qKnz02uSwyhBD/VFbO7DOQ65a1iWk9HngyMsu0Oi7HIbSjSLtxTHedNfOf3waW0NivTTxYXiYGX/MCu3GWhgIGj47a+H3A6FcQ/9QWvnKgnoixdgPBUz7kDb7ktsWwQsILPGStgH7iMuG49ZlXdEFmqwifBri2wvzmFEevBGZjHcupy1IGrNFRG+IUGKMotio+OkLHlLjuv7ZJtqCz/Vf5SNFgNyMSanx6jKEUJuYvndVg99IRXmYVwHFwPu5BAcJACpU6C0AfyGmmSqqwxCd46uXL62ynxNFpHuRfOqlDnmCTfZgjOciJSlDDpf+Xz9fF7+oCoeCi3mrcZVFjhd3tT6Oxw5HrsDtm0ZNld1cdLidaq8H6vOFgHMd0A9yNYZzTzXTvpmxzkXT4Zc7s+PYKN6z5fRZ+pJeckUjRXblvVEfs5HFSymavcOc5AkRwxpvOsTQMNmlnaJCBo5UNs0K/rVmRi5cFmaiwTcBCY0kTllOBJ4zWsfq8seiokWwNUNK2g=
file_glob: true
overwrite: true
file: gam-$GAMVERSION-*
skip_cleanup: true
draft: true
on:
repo: jay0lee/GAM
condition: $TRAVIS_JOB_NAME != *"Testing"

View File

@@ -1,4 +1,6 @@
GAM is a command line tool for Google Workspace (fka G Suite) Administrators to manage domain and user settings quickly and easily. [![Build Status](https://travis-ci.org/jay0lee/GAM.svg?branch=master)](https://travis-ci.org/jay0lee/GAM)
GAM is a command line tool for Google Workspace (fka G Suite) Administrators to manage domain and user settings quickly and easily.
![Build Status](https://github.com/jay0lee/GAM/workflows/Build%20and%20test%20GAM/badge.svg)
# Quick Start
## Linux / MacOS
Open a terminal and run:
@@ -12,8 +14,8 @@ Download the MSI Installer from the [GitHub Releases] page. Install the MSI and
The GAM documentation is hosted in the [GitHub Wiki]
# Mailing List / Discussion group
The GAM mailing list / discussion group is hosted on [Google Groups]. You can join the list and interact via email, or just post from the web itself.
# IM Room
[![Join the chat at https://gitter.im/jay0lee-GAM/community](https://badges.gitter.im/jay0lee-GAM/community.svg)](https://gitter.im/jay0lee-GAM/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# Chat Room
There is a public chat room hosted in Google Chat. [Instructions to join](https://git.io/gam-chat).
# Author
GAM is maintained by <a href="mailto:jay0lee@gmail.com">Jay Lee</a>. Please direct "how do I?" questions to [Google Groups].

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|101037
<SKUID> ::=
cloudidentity|identity|1010010001|
cloudidentitypremium|identitypremium|1010050001|
@@ -93,6 +85,13 @@ If an item contains spaces, it should be surrounded by ".
gams|postini|gsuitegams|gsuitepostini|gsuitemessagesecurity|Google-Apps-For-Postini|
gal|gsl|lite|gsuitelite|Google-Apps-Lite|
gau|gsb|unlimited|gsuitebusiness|Google-Apps-Unlimited|
gwep|workspaceeducationplus|1010310008|
gwepstaff|workspaceeducationplusstaff|1010310009|
gwepstudent|workspaceeducationplusstudent|1010310010|
gwes|workspaceeducationstandard|1010310005|
gwesstaff|workspaceeducationstandardstaff|1010310006|
gwesstudent|workspaceeducationstandardstudent|1010310007|
gwetlu|workspaceeducationupgrade|1010370001|
wsentplus|workspaceenterpriseplus|gae|gse|enterprise|gsuiteenterprise|1010020020|
wsbizplus|workspacebusinessplus|1010020025|
wsentstan|workspaceenterprisestandard|'1010020026|
@@ -103,7 +102,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|
@@ -116,7 +114,8 @@ If an item contains spaces, it should be surrounded by ".
drive8tb|8tb|googledrivestorage8tb|Google-Drive-storage-8TB|
drive16tb|16tb|googledrivestorage16tb|Google-Drive-storage-16TB|
vault|googlevault|Google-Vault|
vfe|googlevaultformeremployee|Google-Vault-Former-Employee
vfe|googlevaultformeremployee|Google-Vault-Former-Employee|
workspacefrontline|workspacefrontlineworker|1010020030
## Basic items built from primitives
@@ -149,13 +148,17 @@ If an item contains spaces, it should be surrounded by ".
<AccessToken> ::= <String>
<ACLScope> ::= [user:]<EmailAddress>|group:<EmailAddress>|domain[:<DomainName>]|default
<APIScopeURL> ::= <String>
<APPID> ::= <String>
<ASPID> ::= <String>
<AssetTag> ::= <String>
<BrowserTokenPermanentID> ::= <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>
<ChatSpace> ::= <String>
<ClientID> ::= <String>
<ColorValue> ::= <ColorName>|<ColorHex>
<CollaboratorItem> ::= <EmailAddress>|<UniqueID>|<String>
@@ -210,11 +213,10 @@ If an item contains spaces, it should be surrounded by ".
<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>
<QueryBrowser> ::= <String> See: https://support.google.com/chrome/a/answer/9681204#retrieve_all_chrome_devices_for_an_account
<QueryBrowserToken> ::= <String> See: https://support.google.com/chrome/a/answer/9949706?ref_topic=9301744
<QueryCalendar> ::= <String>
<QueryContact> ::= <String> See: https://developers.google.com/google-apps/contacts/v3/reference#contacts-query-parameters-reference
<QueryCrOS> ::= <String> See: https://support.google.com/chrome/a/answer/1698333?hl=en
@@ -222,8 +224,7 @@ 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
<QueryTeamDrive> ::= <String> See: https://developers.google.com/drive/api/v3/search-shareddrives
<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
<RequestID> ::= <String>
@@ -492,9 +493,6 @@ If an item contains spaces, it should be surrounded by ".
description|id|inherit|name|orgunitpath|parent|parentid|inherit
<OrgUnitFieldNameList> ::= "<OrgUnitFieldName>(,<OrgUnitFieldName>)*"
<PrintJobOrderByFieldName> ::=
create_time|status|title
<ResourceFieldName> ::=
buildingid|
capacity|
@@ -568,6 +566,7 @@ Items, separated by spaces, with spaces, commas or single quotes in the items th
<ACLList> ::= "<ACLScope>(,<ACLScope>)*"
<APIScopeURLList> ::= "<APIScopeURL>(,<APIScopeURL>)*"
<ASPIDList> ::= "<ASPID>(,<ASPID>)*"
<AssetTagList> ::= "<AssetTag>(,<AssetTa>g)*"
<CalendarList> ::= "<CalendarItem>(,<CalendarItem>)*"
<ChatRoomList> ::= "<ChatRoom>(,<ChatRoom>)*"
<CollaboratorItemList> ::= "<CollaboratorItem>(,<CollaboratorItem>)*"
@@ -594,12 +593,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>)*"
<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 +650,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 +658,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 +666,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,32 +675,36 @@ 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> ::=
(localfile <FileName>)|
<CIGroupAttribute> ::=
(description <String>)|
(name <String>)
<DriveFileAddAttribute> ::=
(localfile <FileName>|-)|
(convert)|(ocr)|(ocrlanguage <Language>)|
(restricted|restrict)|(starred|star)|(trashed|trash)|(viewed|view)|
(contentrestrictions readonly false)|
(contentrestrictions readonly true [reason <String>])|
copyrequireswriterpermission|
(lastviewedbyme <Time>)|(modifieddate|modifiedtime <Time>)|(description <String>)|(mimetype <MimeType>)|
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|writerscanshare
(shortcut <DriveFileID>)
<DriveFileUpdateAttributes> ::=
(localfile <FileName>)|
<DriveFileUpdateAttribute> ::=
(localfile <FileName>|-)|
(convert)|(ocr)|(ocrlanguage <Language>)|
(restricted|restrict <Boolean>)|(starred|star <Boolean>)|(trashed|trash <Boolean>)|(viewed|view <Boolean>)|
(contentrestrictions readonly false)|
(contentrestrictions readonly true [reason <String>])|
(copyrequireswriterpermission <Boolean>)|
(lastviewedbyme <Time>)|(modifieddate <Time>)|(description <String>)|(mimetype <MimeType>)|
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|writerscanshare
(shortcut <DriveFileID>)
<GroupSettingsAttribute> ::=
(allowexternalmembers <Boolean>)|
@@ -773,13 +774,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 +808,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 +867,7 @@ gam <UserTypeEntity> check serviceaccount [scope|scopes <APIScopeURLList>]
gam whatis <EmailItem>
<ResoldCustomerAttributes> ::=
<ResoldCustomerAttribute> ::=
(email|alternateemail <EmailAddress>)|
(contact|contactname <String>)|
(phone|phonenumber <String>)|
@@ -885,7 +880,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>
@@ -901,23 +896,27 @@ gam delete resoldsubscription <CustomerID> <SKUID> cancel|downgrade|transfer_to_
gam info resoldsubscriptions <CustomerID> [customer_auth_token <String>]
<ActivityApplicationName> ::=
access|accesstransparency|
access_transparency|
admin|
calendar|calendars|
calendar|
chat|
drive|doc|docs|
enterprisegroups|groupsenterprise|
chrome|
context_aware_access|
data_studio|
drive|
gcp|
google+|gplus|
group|groups|
hangoutsmeet|meet|
gplus|
groups|
groups_enterprise|
jamboard|
login|logins|
keep|
login|
meet|
mobile|
oauthtoken|token|tokens|
rules|
saml|
useraccounts
token|
user_accounts
<ReportsApp> ::=
accounts|
@@ -976,7 +975,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,12 +990,14 @@ gam print domainaliases|aliasdomains [todrive]
(zipcode|postal|postalcode <String>)|
(country|countrycode <String>)
gam update customer <CustomerAttributes>*
gam update customer <CustomerAttribute>*
gam info customer
<DataTransferService> ::=
calendar|
currents|
datastudio|"google data studio"|
googledrive|gdrive|drive|"drive and docs"
<DataTransferServiceList> ::= "<DataTransferService>(,<DataTransferService>)*"
@@ -1013,15 +1014,15 @@ gam delete org|ou <OrgUnitPath>
gam info org|ou <OrgUnitPath> [nousers|notsuspended|suspended] [children|child]
gam print orgs|ous [todrive] [toplevelonly] [from_parent <OrgUnitPath>] [allfields|(fields <OrgUnitFieldNameList>)]
gam create alias|nickname <EmailAddress> user|group|target <UniqueID>|<EmailAddress>
gam update alias|nickname <EmailAddress> user|group|target <UniqueID>|<EmailAddress>
gam create alias|nickname <EmailAddress> user|group|target <UniqueID>|<EmailAddress> [verifynotinvitable]
gam update alias|nickname <EmailAddress> user|group|target <UniqueID>|<EmailAddress> [verifynotinvitable]
gam delete alias|nickname [user|group|target] <UniqueID>|<EmailAddress>
gam info alias|nickname <EmailAddress>
gam print aliases|nicknames [todrive] [shownoneditable] [nogroups] [nousers] [(query <QueryUser>)|(queries <QueryUserList)]
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> add <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>) [sendnotifications <Boolean>]
gam calendar <CalendarItem> update <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domain|default [sendnotifications <Boolean>]
gam calendar <CalendarItem> del|delete ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domainx|default
gam calendar <CalendarItem> del|delete id <CalendarACLRuleID>
gam calendar <CalendarItem> showacl
gam calendar <CalendarItem> printacl [todrive]
@@ -1034,7 +1035,7 @@ The following attributes are equivalent:
sendnotifications false - sendupdates none
sendnotifications true - sendupdates all
<EventAttributes> ::=
<EventAttribute> ::=
anyonecanaddself|
(attendee <EmailAddress>)|
available|
@@ -1060,8 +1061,8 @@ The following attributes are equivalent:
(timezone <Timezone>)|
(visibility default|public|prvate)
<EventUpdateAttributes> ::=
<EventAttributes>|
<EventUpdateAttribute> ::=
<EventAttribute>|
(removeattendee <EmailAddress>)|
(replacedescription <RegularExpression> <String>)
@@ -1076,10 +1077,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]
@@ -1091,6 +1092,94 @@ gam calendar <CalendarItem> printevents <EventSelectProperty>* <EventDisplayProp
gam calendar <CalendarItem> modify <CalendarSettings>+
<BrowserAttribute> ::=
(assetid <String>)|
(location <String>)|
(notes <String>)|
(user <String>
<BrowserFieldName> ::=
annotatedAssetId|
annotatedLocation|
annotatedNotes|
annotatedUser|
browsers|
browserVersions|
deviceId|
extensionCount|
installedBrowserVersion|
lastActivityTime|
lastDeviceUser|
lastDeviceUsers|
lastPolicyFetchTime|
lastRegistrationTime|
lastStatusReportTime|
machineName|
machinePolicies|
orgUnitPath|
osArchitecture|
osPlatform|
osPlatformVersion|
osVersion|
orgUnitPath|
policyCount|
safeBrowsingClickThroughCount|
serialNumber|
virtualDeviceId
<BrowserFieldNameList> ::= "<BrowseFieldName>(,<BrowserFieldName>)*"
gam move browsers ou|org|orgunit <OrgUnitPath>
((ids <DeviceIDList>) |
(query <QueryBrowser>) |
(file <FileName>) |
(csvfile <FileName>:<FieldName>))
[batchsize <Integer>]
gam update browser <DeviceID> <BrowserAttibute>+
gam info browser <DeviceID>
[basic|full]
[fields <BrowserFieldNameList>]
gam print browsers [todrive]
[ou|org|orgunit <OrgUnitPath>] [query <QueryBrowser>]
[projection basic|full]
[fields <BrowserFieldNameList>]
[sortheaders]
gam create browsertoken
[ou|org|orgunit <OrgUnitPath>] [expire|expires <Time>]
gam revoke browsertoken <BrowserTokenPermanentID>
<BrowserTokenFieldName> ::=
createTime|
creatorId|
customerId|
expireTime|
orgUnitPath|
revokeTime|
revokerId|
state|
token|
tokenPermanentId
<BrowserTokenFieldNameList> ::= "<BrowseTokenFieldName>(,<BrowserTokenFieldName>)*"
gam show browsertokens
[query <QueryBrowserToken>]
[fields <BrowserTokenFieldNameList>]
gam print browsertokens [todrive]
[query <QueryBrowserToken>]
[fields <BrowserTokenFieldNameList>]
[sortheaders]
gam print chatspaces [todrive]
gam print chatmembers space <ChatSpace> [todrive]
gam create chatmessage space <ChatSpace> [thread <String>]
(text <String>)|(textfile <FileName> [charset <CharSet>])
gam delete chatmessage name <String>
gam update chatmessage name <String>
(text <String>)|(textfile <FileName> [charset <CharSet>])
<CrOSAction> ::=
deprovision_same_model_replace|
deprovision_different_model_replace|
@@ -1099,7 +1188,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>]
@@ -1147,6 +1248,69 @@ The listlimit <Number> argument limits the number of recent users, time ranges a
The start <Date> and end <Date> arguments filter the time ranges.
Delimiter defaults to comma.
gam print chromeapps [todrive]
[ou|org|orgunit <OrgUnitItem>]
[filter <String>]
[orderby appname|apptype|installtype|numberofpermissions|totalinstallcount]
gam print chromeappdevices [todrive]
appid <AppID> apptype extension|app|theme|hostedapp|androidapp
[ou|org|orgunit <OrgUnitItem>]
[start <Date>] [end <Date>]
[orderby deviceid|machine]
gam print chromeversions [todrive]
[ou|org|orgunit <OrgUnitItem>]
[start <Date>] [end <Date>] [recentfirst]
<ChromePlatformType>> ::=
all'|
android'|
ios'|
lacros'|
linux'|
mac'|
macarm64'|
sebview'|
win'|
win64'
<ChromeChannelType> ::=
beta'|
canary'|
canaryasan'|
dev'|
stable'
<ChromeVersionsOrderByFieldName> ::=
channel|
name|
platform|
version|
<ChromeReleasesOrderByFieldName> ::=
channel|
endtime|
fraction|
name|
platform|
starttime|
version
gam print chromehistory platforms [todrive]
gam print chromehistory channels [todrive]
[platform <ChromePlatformType>]
gam print chromehistory versions [todrive]
[platform <ChromePlatformType>] [channel <ChromeChannelType>]
[filter <String>]
(orderby <ChromeVersionsOrderByFieldName> [ascending|descending])*
gam print chromehistory releases [todrive]
[platform <ChromePlatformType>] [channel <ChromeChannelType>] [version <String>]
[filter <String>]
(orderby <ChromeReleasessOrderByFieldName> [ascending|descending])*
gam delete chromepolicy <SchemaName>+ ou|org|orgunit <OrgUnitItem> [(printerid <PrinterID>)|(appid <AppID>)]
gam update chromepolicy (<SchemaName> (<Field> <Value>)+)+ ou|org|orgunit <OrgUnitItem> [(printerid <PrinterID>)|(appid <AppID>)]
gam show chromepolicy ou|org|orgunit <OrgUnitItem> [(printerid <PrinterID>)|(appid <AppID>)]
gam show chromeschema [filter <String>]
<DeviceID> ::= devices/<String>
<DeviceType> ::= android|chrome_os|google_sync|ios|linux|mac_os|windows
<DeviceUserID> ::= devices/<String>/deviceUsers/<String>
@@ -1158,11 +1322,6 @@ gam info device [id] <DeviceID>
gam delete device [id] <DeviceID>
gam cancelwipe device [id] <DeviceID>
gam wipe device [id] <DeviceID>
gam approve deviceuser [id] <DeviceUserID>
gam block deviceuser [id] <DeviceUserID>
gam delete deviceuser [id] <DeviceUserID>
gam cancelwipe deviceuser [id] <DeviceUserID>
gam wipe deviceuser [id] <DeviceUserID>
gam print devices [todrive] [filter|query <QueryDevice>]
[orderby <DeviceOrderByFieldName> [ascending|descending]]
[company|personal|nocompanydevices|nopersonaldevices]
@@ -1175,14 +1334,65 @@ gam sync devices [filter|query <QueryDevice>]
[unassigned_missing_action delete|wipe|donothing]
[assigned_missing_action delete|wipe|donothing]
gam approve deviceuser [id] <DeviceUserID>
gam block deviceuser [id] <DeviceUserID>
gam delete deviceuser [id] <DeviceUserID>
gam cancelwipe deviceuser [id] <DeviceUserID>
gam wipe deviceuser [id] <DeviceUserID>
gam info deviceuserstate [id] <DeviceUserID> [clientid <String>]
gam update deviceuserstate [id] <DeviceUserID> [clientid <String>]
[customid <String>] [assettags clear|<AssetTagList>]
[compliantstate|compliancestate compliant|noncompliant] [managedstate clear|managed|unmanaged]
[healthscore very_poor|poor|neutral|good|very_good] [scorereason clear|<String>]
(customvalue (bool <Boolean>)|(number <Integer>)|(string <String>))*
gam update mobile <MobileID>|query:<QueryMobile> action <MobileAction> [doit] [if_users|match_users <UserTypeEntity>]
gam delete mobile <MobileID>
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>*
<PrinterAttribute> ::=
(description <String>)|
(displayname <String>)|
(makeandmodel <String>)|
(ou|org|orgunit|orgunitid <OrgUnitItem>)|
(ownerid <EmailAddress>)|
(uri <String>)|
(driverless|usedriverlessconfig)
gam create printer <PrinterAttribute>+
gam update printer <PrinterID> <PrinterAttribute>+
gam delete printer <PrinterIDList>|(file <FileName>)|(csvfile <FileName>:<FieldName>)
gam info printer <PrinterID>
gam print printers [todrive] [filter <String>]
gam print printermodels [todrive] [filter <String>]
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] [expires never|<Time>] <UserTypeEntity>
gam update cigroup <GroupItem> delete|remove [owner|manager|member] [notsuspended|suspended] <UserTypeEntity>
gam update cigroup <GroupItem> sync [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
gam update cigroup <GroupItem> update [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
gam update cigroup <GroupItem> clear [member] [manager] [owner] [notsuspended|suspended]
gam delete cigroup <GroupItem>
gam info cigroup <GroupItem> [nousers] [nojoindate] [showupdatedate] [membertree]
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>* [verifynotinvitable]
gam update group <GroupItem> [email <EmailAddress>] <GroupAttribute>* [verifynotinvitable]
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>
@@ -1202,11 +1412,20 @@ gam print group-members|groups-members [todrive]
[roles <GroupRoleList>] [membernames] [fields <MembersFieldNameList>]
[includederivedmembership]
gam send userinvitation <EmailAddress>
gam cancel userinvitation <EmailAddress>
gam check userinvitation|isinvitable <EmailAddress>
gam info userinvitation <EmailAddress>
gam print userinvitations [todrive]
[state notyetsent|invited|accepted|declined]]
[orderby email|updatetime [ascending|descending]]
gam <UserTypeEntity> check isinvitable [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 +1435,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,11 +1448,11 @@ 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>* [verifynotinvitable]
gam update user <UserItem> <UserAttribute>* [clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>] [verifynotinvitable]
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>]
gam info user [<UserItem>] [noaliases] [nogroups] [nolicenses|nolicences] [noschemas] [schemas|custom <SchemaNameList>] [userview] [skus|sku <SKUIDList>] [grouptree]
Print fields for selected users; use domain, query/queries and deleted_only to select users to print;
if none of these options are specified, all users are printed.
@@ -1251,8 +1470,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 +1491,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>]
@@ -1333,6 +1526,18 @@ gam undelete vaultmatter|matter <MatterItem>
gam info vaultmatter|matter <MatterItem>
gam print vaultmatters|matters [todrive] [basic|full] [matterstate open|closed|deleted]
gam print vaultcounts [todrive]
matter <MatterItem> corpus mail|groups
(accounts <EmailAddressEntity>) | (orgunit|org|ou <OrgUnitPath>) | everyone
[scope <all_data|held_data|unprocessed_data>]
[terms <terms>] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] [timezone <TimeZone>]
[excludedrafts <Boolean>]
[wait <Integer>]
gam print vaultcounts [todrive]
matter <MatterItem> operation <String>
[wait <Integer>]
gam <UserTypeEntity> delete|del asp|asps|applicationspecificpasswords all|<ASPIDList>
gam <UserTypeEntity> show asps|asp|applicationspecificpasswords
@@ -1340,8 +1545,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 +1565,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 +1603,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
@@ -1409,6 +1614,7 @@ gam <UserTypeEntity> update labelsettings <LabelName> [name <Name>] [messagelist
gam <UserTypeEntity> update label|labels [search <RegularExpression>] [replace <LabelReplacement>] [merge]
gam <UserTypeEntity> delete|del label|labels <LabelName>|regex:<RegularExpression>|--ALL_LABELS--
gam <UserTypeEntity> show labels|label [onlyuser] [showcounts]
gam <UserTypeEntity> print labels|label [todrive] [onlyuser] [showcounts]
gam <UserTypeEntity> delete messages query <QueryGmail> [doit] [max_to_delete|max_to_process <Number>]
gam <UserTypeEntity> modify messages query <QueryGmail> [doit] [max_to_modify|max_to_process <Number>] (addlabel <LabelName>)* (removelabel <LabelName>)*
@@ -1432,12 +1638,17 @@ 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>
gam <UserTypeEntity> create|add delegate|delegates [convertalias] <EmailAddress>
gam <UserTypeEntity> delegate|delegates to [convertalias] <EmailAddress>
gam <UserTypeEntity> delete|del delegate|delegates [convertalias] <EmailAddress>
gam <UserTypeEntity> show delegates|delegate [csv]
gam <UserTypeEntity> print delegates [todrive]
gam <UserTypeEntity> create|add contactdelegate <EmailAddress>
gam <UserTypeEntity> delete|del contactdelegate <EmailAddress>
gam <UserTypeEntity> show contactdelegates [csv]
gam <UserTypeEntity> print contactdelegates [todrive]
gam <UserTypeEntity> [create|add] filter [from <EmailAddress>] [to <EmailAddress>] [subject <String>] [haswords|query <List>] [nowords|negatedquery <List>] [musthaveattachment|hasattachment] [excludechats] [size larger|smaller <ByteCount>]
[label <LabelID>] [important|notimportant] [star] [trash] [markread] [archive] [neverspam] [forward <EmailAddress>]
gam <UserTypeEntity> delete filters <FilterIDEntity>
@@ -1494,8 +1705,8 @@ gam <UserTypeEntity> update teamdrive <TeamDriveID> [asadmin] [name <Name>]
(<TeamDriveRestrictionsSubfieldName> <Boolean>)*
gam <UserTypeEntity> delete teamdrive <TeamDriveID>
gam <UserTypeEntity> show teamdriveinfo <TeamDriveID> [asadmin]
gam <UserTypeEntity> show teamdrives [asadmin]
gam <UserTypeEntity> print teamdrives [todrive] [asadmin]
gam <UserTypeEntity> show teamdrives [query <QueryTeamDrive>] [asadmin]
gam <UserTypeEntity> print teamdrives [query <QueryTeamDrive>] [todrive] [asadmin]
gam <UserTypeEntity> show teamdrivethemes
gam <UserTypeEntity> vacation <FalseValues>

587
src/cbcm-v1.1beta1.json Normal file
View File

@@ -0,0 +1,587 @@
{
"auth": {
"oauth2": {
"scopes": {
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers": {
"description": "View and manage your Chrome browsers registered with Cloud Management"
},
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly": {
"description": "View your Chrome browsers registered with Cloud Management"
}
}
}
},
"basePath": "",
"baseUrl": "https://www.googleapis.com/admin/directory/v1.1beta1/customer/",
"batchPath": "batch",
"canonicalName": "cbcm",
"discoveryVersion": "v1",
"documentationLink": "https://support.google.com/chrome/a/answer/9681204",
"fullyEncodeReservedExpansion": true,
"icons": {
"x16": "http://www.google.com/images/icons/product/search-16.gif",
"x32": "http://www.google.com/images/icons/product/search-32.gif"
},
"id": "cbcm:v1.1beta1",
"kind": "discovery#restDescription",
"mtlsRootUrl": "https://admin.mtls.googleapis.com/",
"name": "cbcm",
"ownerDomain": "google.com",
"ownerName": "Jay Lee",
"packagePath": "cbcm",
"parameters": {
"$.xgafv": {
"description": "V1 error format.",
"enum": [
"1",
"2"
],
"enumDescriptions": [
"v1 error format",
"v2 error format"
],
"location": "query",
"type": "string"
},
"access_token": {
"description": "OAuth access token.",
"location": "query",
"type": "string"
},
"alt": {
"default": "json",
"description": "Data format for response.",
"enum": [
"json",
"media",
"proto"
],
"enumDescriptions": [
"Responses with Content-Type of application/json",
"Media download with context-dependent Content-Type",
"Responses with Content-Type of application/x-protobuf"
],
"location": "query",
"type": "string"
},
"callback": {
"description": "JSONP",
"location": "query",
"type": "string"
},
"fields": {
"description": "Selector specifying which fields to include in a partial response.",
"location": "query",
"type": "string"
},
"key": {
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
"location": "query",
"type": "string"
},
"oauth_token": {
"description": "OAuth 2.0 token for the current user.",
"location": "query",
"type": "string"
},
"prettyPrint": {
"default": "true",
"description": "Returns response with indentations and line breaks.",
"location": "query",
"type": "boolean"
},
"quotaUser": {
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
"location": "query",
"type": "string"
},
"uploadType": {
"description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
"location": "query",
"type": "string"
},
"upload_protocol": {
"description": "Upload protocol for media (e.g. \"raw\", \"multipart\").",
"location": "query",
"type": "string"
}
},
"protocol": "rest",
"resources": {
"chromebrowsers": {
"methods": {
"delete": {
"description": "Deletes a browser.",
"flatPath": "{customer}/devices/chromebrowsers/{deviceId}",
"httpMethod": "DELETE",
"id": "cbcm.chromebrowsers.delete",
"parameterOrder": [
"customer",
"deviceId"
],
"parameters": {
"customer": {
"description": "Immutable ID of the G Suite account.",
"location": "path",
"required": true,
"type": "string"
},
"deviceId": {
"description": "Immutable ID of the browser.",
"location": "path",
"required": true,
"type": "string"
}
},
"path": "{customer}/devices/chromebrowsers/{deviceId}",
"scopes": [
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
]
},
"get": {
"description": "Retrieves a browser.",
"flatPath": "{customer}/devices/chromebrowsers/{deviceId}",
"httpMethod": "GET",
"id": "cbcm.chromebrowsers.get",
"parameterOrder": [
"customer",
"deviceId"
],
"parameters": {
"customer": {
"description": "Immutable ID of the G Suite account.",
"location": "path",
"required": true,
"type": "string"
},
"deviceId": {
"description": "Immutable ID of the browser.",
"location": "path",
"required": true,
"type": "string"
},
"projection": {
"description": "Restrict information returned to a set of selected fields. FULL or BASIC.",
"location": "query",
"type": "string"
}
},
"path": "{customer}/devices/chromebrowsers/{deviceId}",
"response": {
"$ref": "ChromeBrowser"
},
"scopes": [
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers",
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly"
]
},
"list": {
"description": "Retrieves a paginated list of all the browsers in a domain.",
"flatPath": "{customer}/devices/chromebrowsers",
"httpMethod": "GET",
"id": "cbcm.chromebrowsers.list",
"parameterOrder": [
"customer"
],
"parameters": {
"customer": {
"description": "Immutable ID of the G Suite account.",
"location": "path",
"required": true,
"type": "string"
},
"maxResults": {
"description": "Maximum number of results to return.",
"format": "int32",
"location": "query",
"maximum": "100",
"minimum": "1",
"type": "integer"
},
"orderBy": {
"description": "property to use for sorting results.",
"location": "query",
"type": "string"
},
"orgUnitPath": {
"description": "The full path of the organizational unit or its unique ID.",
"location": "query",
"type": "string"
},
"pageToken": {
"description": "Token to specify the next page in the list.",
"location": "query",
"type": "string"
},
"projection": {
"description": "Restrict information returned to a set of selected fields. FULL or BASIC.",
"location": "query",
"type": "string"
},
"query": {
"description": "Search string using the list page query language.",
"location": "query",
"type": "string"
},
"sortOrder": {
"description": "Whether to return results in ascending or descending order. Must be used with the orderBy parameter.",
"location": "query",
"type": "string"
}
},
"path": "{customer}/devices/chromebrowsers",
"response": {
"$ref": "ChromeBrowsers"
},
"scopes": [
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers",
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly"
]
},
"moveChromeBrowsersToOu": {
"description": "Move Chrome Browsers Device between Organization Units",
"flatPath": "{customer}/devices/chromebrowsers/moveChromeBrowsersToOu",
"httpMethod": "POST",
"id": "cbcm.chromebrowsers.moveChromeBrowsersToOu",
"parameterOrder": [
"customer"
],
"parameters": {
"customer": {
"description": "Immutable ID of the G Suite account.",
"location": "path",
"required": true,
"type": "string"
}
},
"path": "{customer}/devices/chromebrowsers/moveChromeBrowsersToOu",
"request": {
"$ref": "MoveChromeBrowsersRequest"
},
"scopes": [
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
]
},
"update": {
"description": "Updates a browser.",
"flatPath": "{customer}/devices/chromebrowsers/{deviceId}",
"httpMethod": "PUT",
"id": "cbcm.chromebrowsers.update",
"parameterOrder": [
"customer",
"deviceId"
],
"parameters": {
"customer": {
"description": "Immutable ID of the G Suite account.",
"location": "path",
"required": true,
"type": "string"
},
"deviceId": {
"description": "Immutable ID of the browser.",
"location": "path",
"required": true,
"type": "string"
},
"projection": {
"description": "BASIC or FULL",
"location": "query",
"type": "string"
}
},
"path": "{customer}/devices/chromebrowsers/{deviceId}",
"request": {
"$ref": "ChromeBrowser"
},
"response": {
"$ref": "ChromeBrowser"
},
"scopes": [
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
]
}
}
},
"enrollmentTokens": {
"methods": {
"list": {
"description": "Retrieves a paginated list of all the browser entollment tokens in a domain.",
"flatPath": "{customer}/chrome/enrollmentTokens",
"httpMethod": "GET",
"id": "cbcm.enrollmentTokens.list",
"parameterOrder": [
"customer"
],
"parameters": {
"customer": {
"description": "Immutable ID of the G Suite account.",
"location": "path",
"required": true,
"type": "string"
},
"pageSize": {
"description": "Maximum number of results to return.",
"format": "int32",
"location": "query",
"maximum": "100",
"minimum": "1",
"type": "integer"
},
"orgUnitPath": {
"description": "The full path of the organizational unit or its unique ID.",
"location": "query",
"type": "string"
},
"pageToken": {
"description": "Token to specify the next page in the list.",
"location": "query",
"type": "string"
},
"query": {
"description": "Search string using the list page query language.",
"location": "query",
"type": "string"
}
},
"path": "{customer}/chrome/enrollmentTokens",
"response": {
"$ref": "EnrollmentTokens"
},
"scopes": [
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers",
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly"
]
},
"create": {
"description": "Creates a browser enrollment token in a domain.",
"flatPath": "{customer}/chrome/enrollmentTokens",
"httpMethod": "POST",
"id": "cbcm.enrollmentTokens.create",
"parameterOrder": [
"customer"
],
"parameters": {
"customer": {
"description": "Immutable ID of the G Suite account.",
"location": "path",
"required": true,
"type": "string"
}
},
"path": "{customer}/chrome/enrollmentTokens",
"request": {
"$ref": "CreateEnrollmentTokenRequest"
},
"response": {
"$ref": "EnrollmentToken"
},
"scopes": [
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
]
},
"revoke": {
"description": "Revokes a browser enrollment token in a domain.",
"flatPath": "{customer}/chrome/enrollmentTokens/{tokenPermanentId}:revoke",
"httpMethod": "POST",
"id": "cbcm.enrollmentTokens.revoke",
"parameterOrder": [
"customer",
"tokenPermanentId"
],
"parameters": {
"customer": {
"description": "Immutable ID of the G Suite account.",
"location": "path",
"required": true,
"type": "string"
},
"tokenPermanentId": {
"description": "Unique identifier for an enrollment token.",
"location": "path",
"required": true,
"type": "string"
}
},
"path": "{customer}/chrome/enrollmentTokens/{tokenPermanentId}:revoke",
"scopes": [
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
]
}
}
}
},
"revision": "20201203",
"rootUrl": "https://www.googleapis.com/admin/directory/v1.1beta1/customer/",
"schemas": {
"ChromeBrowser": {
"id": "ChromeBrowser",
"properties": {
"annotatedAssetId": {
"description": "Asset identifier as annotated by the administrator or specified during enrollment.",
"type": "string"
},
"annotatedLocation": {
"description": "Address or location of the device as annotated by the administrator.",
"type": "string"
},
"annotatedNotes": {
"description": "Notes about this device as annotated by the administrator",
"type": "string"
},
"annotatedUser": {
"description": "User of the device as annotated by the administrator.",
"type": "string"
},
"deviceId": {
"annotations": {
"required": [
"cbcm.chromebrowsers.update"
]
},
"description": "The unique ID of the device.",
"type": "string"
}
},
"type": "object"
},
"ChromeBrowsers": {
"id": "ChromeBrowsers",
"properties": {
"browsers": {
"description": "List of Chrome browser objects.",
"items": {
"$ref": "ChromeBrowser"
},
"type": "array"
},
"etag": {
"description": "ETag of the resource.",
"type": "string"
},
"kind": {
"default": "admin#directory#chromeosdevices",
"description": "Kind of resource this is.",
"type": "string"
},
"nextPageToken": {
"description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.",
"type": "string"
}
},
"type": "object"
},
"EnrollmentToken": {
"id": "EnrollmentToken",
"properties": {
"kind": {
"default": "admin#directory#chromeEnrollmentToken",
"description": "Kind of resource this is.",
"type": "string"
},
"tokenId": {
"description": "Enrollment Token ID.",
"type": "string"
},
"tokenPermanentId": {
"description": "Enrollment Token Permanent ID.",
"type": "string"
},
"customerId": {
"description": "Immutable ID of the G Suite account.",
"type": "string"
},
"orgUnitPath": {
"description": "The full path of the organizational unit or its unique ID.",
"type": "string"
},
"creatorId": {
"description": "Creator ID.",
"type": "string"
},
"createTime": {
"description": "Creation Time.",
"type": "string"
},
"revokerId": {
"description": "Revoker ID.",
"type": "string"
},
"revokeTime": {
"description": "Revoke Time",
"type": "string"
}
},
"type": "object"
},
"EnrollmentTokens": {
"id": "EnrollmentTokens",
"properties": {
"chrome_enrollment_tokens": {
"description": "List of Chrome browser enrollment token objects.",
"items": {
"$ref": "EnrollmentToken"
},
"type": "array"
},
"kind": {
"default": "admin#directory#chromeEnrollmentTokens",
"description": "Kind of resource this is.",
"type": "string"
},
"nextPageToken": {
"description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.",
"type": "string"
}
},
"type": "object"
},
"CreateEnrollmentTokenRequest": {
"id": "CreateEnrollmentTokenRequest",
"properties": {
"org_unit_path": {
"description": "The full path of the organizational unit or its unique ID.",
"type": "string"
},
"expire_time": {
"description": "Expiration Time.",
"type": "string"
},
"token_type": {
"annotations": {
"required": [
"cbcm.enrollmentTokens.create"
]
},
"description": "CHROME_BROWSER.",
"type": "string"
}
}
},
"MoveChromeBrowsersRequest": {
"properties": {
"org_unit_path": {
"annotations": {
"required": [
"cbcm.chromebrowsers.moveChromeBrowsersToOu"
]
},
"description": "Destination organization unit to move devices to. Full path of the organizational unit or its ID prefixed with id:",
"type": "string"
},
"resource_ids": {
"annotations": {
"required": [
"cbcm.chromebrowsers.moveChromeBrowsersToOu"
]
},
"description": "List of unique device IDs of Chrome Browser Devices to move. A maximum of 600 browsers may be moved per request.",
"type": "array"
}
}
}
},
"servicePath": "",
"title": "Admin SDK API",
"version": "cbcm_v1.1beta1"
}

View File

@@ -0,0 +1,249 @@
{
"auth": {
"oauth2": {
"scopes": {
"https://www.googleapis.com/auth/admin.contact.delegation": {
"description": "View and manage your Contact Delegation"
},
"https://www.googleapis.com/auth/admin.contact.delegation.readonly": {
"description": "View your Contact Delegation"
}
}
}
},
"basePath": "",
"baseUrl": "https://admin.googleapis.com/admin/contacts/v1/",
"batchPath": "batch",
"canonicalName": "contactdelegation",
"description": "The Contact Delegation API allows Admins to delegate access of one user's, called the delegator, contacts to another user, called the delegate.",
"discoveryVersion": "v1",
"documentationLink": "https://developers.google.com/admin-sdk/contact-delegation",
"fullyEncodeReservedExpansion": true,
"icons": {
"x16": "http://www.google.com/images/icons/product/search-16.gif",
"x32": "http://www.google.com/images/icons/product/search-32.gif"
},
"id": "contactdelegation:v1",
"kind": "discovery#restDescription",
"name": "contactdelegation",
"ownerDomain": "google.com",
"ownerName": "Google",
"packagePath": "admin",
"parameters": {
"$.xgafv": {
"description": "V1 error format.",
"enum": [
"1",
"2"
],
"enumDescriptions": [
"v1 error format",
"v2 error format"
],
"location": "query",
"type": "string"
},
"access_token": {
"description": "OAuth access token.",
"location": "query",
"type": "string"
},
"alt": {
"default": "json",
"description": "Data format for response.",
"enum": [
"json",
"media",
"proto"
],
"enumDescriptions": [
"Responses with Content-Type of application/json",
"Media download with context-dependent Content-Type",
"Responses with Content-Type of application/x-protobuf"
],
"location": "query",
"type": "string"
},
"callback": {
"description": "JSONP",
"location": "query",
"type": "string"
},
"fields": {
"description": "Selector specifying which fields to include in a partial response.",
"location": "query",
"type": "string"
},
"key": {
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
"location": "query",
"type": "string"
},
"oauth_token": {
"description": "OAuth 2.0 token for the current user.",
"location": "query",
"type": "string"
},
"prettyPrint": {
"default": "true",
"description": "Returns response with indentations and line breaks.",
"location": "query",
"type": "boolean"
},
"quotaUser": {
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
"location": "query",
"type": "string"
},
"uploadType": {
"description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
"location": "query",
"type": "string"
},
"upload_protocol": {
"description": "Upload protocol for media (e.g. \"raw\", \"multipart\").",
"location": "query",
"type": "string"
}
},
"protocol": "rest",
"resources": {
"delegates": {
"methods": {
"create": {
"description": "Creates a contact delegations",
"flatPath": "users/{user}/delegates",
"httpMethod": "POST",
"id": "contactdelegations.delegates.create",
"parameterOrder": [
"user"
],
"parameters": {
"user": {
"description": "Email address of the delegator.",
"location": "path",
"required": true,
"type": "string"
}
},
"path": "users/{user}/delegates/{delegate}",
"request": {
"$ref": "Delegate"
},
"scopes": [
"https://www.googleapis.com/auth/admin.contact.delegation"
]
},
"delete": {
"description": "Deletes a contact delegation.",
"flatPath": "users/{user}/delegates/{delegate}",
"httpMethod": "DELETE",
"id": "contactdelegations.delegates.delete",
"parameterOrder": [
"user",
"delegate"
],
"parameters": {
"delegate": {
"description": "Email address of the delegate",
"location": "path",
"required": true,
"type": "string"
},
"user": {
"description": "Email address of the delegator.",
"location": "path",
"required": true,
"type": "string"
}
},
"path": "users/{user}/delegates/{delegate}",
"scopes": [
"https://www.googleapis.com/auth/admin.contact.delegation"
]
},
"list": {
"description": "Lists contact delegates for a user",
"flatPath": "users/{user}/delegates",
"httpMethod": "GET",
"id": "contactdelegations.delegates.list",
"parameterOrder": [
"user"
],
"parameters": {
"pageSize": {
"description": "Determines how many delegates are returned in each response. ",
"format": "int32",
"location": "query",
"minimum": "1",
"type": "integer"
},
"pageToken": {
"description": "Token to specify the next page in the list.",
"location": "query",
"type": "string"
},
"user": {
"description": "Email address of the delegator.",
"location": "path",
"required": true,
"type": "string"
}
},
"path": "users/{user}/delegates",
"response": {
"$ref": "Delegates"
},
"scopes": [
"https://www.googleapis.com/auth/admin.contact.delegation",
"https://www.googleapis.com/auth/admin.contact.delegation.readonly"
]
}
}
}
},
"rootUrl": "https://admin.googleapis.com/admin/contacts/v1/",
"schemas": {
"Delegate": {
"description": "JSON template for a delegate.",
"id": "Delegate",
"properties": {
"email": {
"description": "Email of the delegate.",
"type": "string"
}
},
"type": "object"
},
"Delegates": {
"id": "Delegates",
"properties": {
"delegates": {
"description": "List of delegates.",
"items": {
"$ref": "Delegate"
},
"type": "array"
},
"etag": {
"description": "ETag of the resource.",
"type": "string"
},
"kind": {
"default": "",
"description": "Kind of resource this is.",
"type": "string"
},
"nextPageToken": {
"description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.",
"type": "string"
}
},
"type": "object"
}
},
"servicePath": "",
"title": "Contact Delegation API",
"version": "v1",
"version_module": true
}

View File

@@ -29,7 +29,7 @@ gamversion="latest"
adminuser=""
regularuser=""
gam_glibc_vers="2.31 2.27 2.23"
gam_macos_vers="10.15.6 10.14.6 10.13.6"
#gam_macos_vers="10.15.6 10.14.6 10.13.6"
while getopts "hd:a:o:b:lp:u:r:v:" OPTION
do
@@ -128,19 +128,12 @@ case $gamos in
this_macos_ver=$osversion
fi
echo "You are running MacOS $this_macos_ver"
use_macos_ver=""
for gam_macos_ver in $gam_macos_vers; do
if version_gt $this_macos_ver $gam_macos_ver; then
use_macos_ver="MacOS$gam_macos_ver"
echo_green "Using GAM compiled on $use_macos_ver"
break
fi
done
if [ "$use_macos_ver" == "" ]; then
echo_red "Sorry, you need to be running at least MacOS $gam_macos_ver to run GAM"
exit
fi
gamfile="macos-x86_64-$use_macos_ver.tar.xz"
gamfile="macos-x86_64.tar.xz"
;;
MINGW64_NT*)
gamos="windows"
echo "You are running Windows"
gamfile="-windows-x86_64.zip"
;;
*)
echo_red "Sorry, this installer currently only supports Linux and MacOS. Looks like you're runnning on $gamos. Exiting."
@@ -154,8 +147,14 @@ else
release_url="https://api.github.com/repos/jay0lee/GAM/releases/tags/v$gamversion"
fi
echo_yellow "Checking GitHub URL $release_url for $gamversion GAM release..."
release_json=$(curl -s $release_url 2>&1 /dev/null)
if [ -z ${GHCLIENT+x} ]; then
check_type="unauthenticated"
else
check_type="authenticated"
fi
echo_yellow "Checking GitHub URL $release_url for $gamversion GAM release ($check_type)..."
release_json=$(curl -s $GHCLIENT $release_url 2>&1 /dev/null)
echo_yellow "Getting file and download URL..."
# Python is sadly the nearest to universal way to safely handle JSON with Bash
@@ -222,14 +221,18 @@ temp_archive_dir=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir')
# Clean up after ourselves even if we are killed with CTRL-C
trap "rm -rf $temp_archive_dir" EXIT
echo_yellow "Downloading file $name from $browser_download_url to $temp_archive_dir."
echo_yellow "Downloading file $name from $browser_download_url to $temp_archive_dir ($check_type)..."
# Save archive to temp w/o losing our path
(cd $temp_archive_dir && curl -O -L $browser_download_url)
(cd $temp_archive_dir && curl -O -L $GHCLIENT $browser_download_url)
mkdir -p "$target_dir"
echo_yellow "Extracting archive to $target_dir"
tar xf $temp_archive_dir/$name -C "$target_dir"
if [[ "${name}" == *.tar.xz ]]; then
tar xf $temp_archive_dir/$name -C "$target_dir"
else
unzip "${temp_archive_dir}/${name}" -d "${target_dir}"
fi
rc=$?
if (( $rc != 0 )); then
echo_red "ERROR: extracting the GAM archive with tar failed with error $rc. Exiting."

View File

@@ -12,9 +12,16 @@ proot = os.path.dirname(importlib.import_module('httplib2').__file__)
extra_files = [(os.path.join(proot, 'cacerts.txt'), 'httplib2')]
extra_files += copy_metadata('google-api-python-client')
extra_files += [('cbcm-v1.1beta1.json', '.')]
extra_files += [('contactdelegation-v1.json', '.')]
extra_files += [('versionhistory-v1.json', '.')]
hidden_imports = [
'gam.auth.yubikey',
]
a = Analysis(['gam/__main__.py'],
hiddenimports=[],
hiddenimports=hidden_imports,
hookspath=None,
excludes=['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'],
datas=extra_files,
@@ -27,6 +34,7 @@ for d in a.datas:
pyz = PYZ(a.pure)
exe = EXE(pyz,
a.scripts,
a.binaries,
@@ -36,4 +44,4 @@ exe = EXE(pyz,
debug=False,
strip=None,
upx=False,
console=True )
console=True)

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,9 @@ import os
from google.auth.jwt import Credentials as JWTCredentials
import gam
from gam import utils
from gam.auth import oauth
from gam.var import _FN_OAUTH2_TXT
from gam.var import _FN_OAUTH2SERVICE_JSON
@@ -13,6 +16,7 @@ from gam.var import GC_OAUTH2SERVICE_JSON
from gam.var import GC_ENABLE_DASA
from gam.var import GC_Values
yubikey = utils.LazyLoader('yubikey', globals(), 'gam.auth.yubikey')
# TODO: Move logic that determines file name into this module. We should be able
# to discover the file location without accessing a private member or waiting
# for a global initialization.
@@ -36,10 +40,17 @@ def get_admin_credentials(api=None):
with open(credential_file, 'r') as f:
creds_data = json.load(f)
# Validate that enable DASA matches content of authorization file
if GC_Values[GC_ENABLE_DASA] and 'private_key' in creds_data:
if GC_Values[GC_ENABLE_DASA] and 'private_key_id' in creds_data:
audience = f'https://{api}.googleapis.com/'
return JWTCredentials.from_service_account_info(creds_data,
audience=audience)
key_type = creds_data.get('key_type', 'default')
if key_type == 'default':
return JWTCredentials.from_service_account_info(creds_data,
audience=audience)
elif key_type == 'yubikey':
yksigner = yubikey.YubiKey(creds_data)
return JWTCredentials._from_signer_and_info(yksigner,
creds_data,
audience=audience)
elif not GC_Values[GC_ENABLE_DASA] and 'token' in creds_data:
return oauth.Credentials.from_credentials_file(credential_file)
else:

74
src/gam/auth/yubikey.py Normal file
View File

@@ -0,0 +1,74 @@
from base64 import b64encode
import sys
from threading import Timer
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from ykman.device import connect_to_device
from yubikit.piv import KEY_TYPE, SLOT, InvalidPinError, PivSession
from yubikit.core.smartcard import ApduError
from gam import controlflow
class YubiKey():
def __init__(self, service_account_info):
key_type = service_account_info.get('yubikey_key_type', 'RSA2048')
try:
self.key_type = getattr(KEY_TYPE, key_type.upper())
except AttributeError:
controlflow.system_error_exit(6, f'{key_type} is not a valid value for yubikey_key_type')
slot = service_account_info.get('yubikey_slot', 'AUTHENTICATION')
try:
self.slot = getattr(SLOT, slot.upper())
except AttributeError:
controlflow.system_error_exit(6, f'{slot} is not a valid value for yubikey_slot')
self.serial_number = service_account_info.get('yubikey_serial_number')
self.pin = service_account_info.get('yubikey_pin')
self.key_id = service_account_info.get('private_key_id')
def get_certificate(self):
try:
conn, _, _ = connect_to_device(self.serial_number)
session = PivSession(conn)
if self.pin:
try:
session.verify_pin(self.pin)
except InvalidPinError as err:
controlflow.system_error_exit(7, f'YubiKey - {err}')
try:
cert = session.get_certificate(self.slot)
cert_pem = cert.public_bytes(
serialization.Encoding.PEM).decode()
publicKeyData = b64encode(cert_pem.encode())
if isinstance(publicKeyData, bytes):
publicKeyData = publicKeyData.decode()
return publicKeyData
except ApduError as err:
controlflow.system_error_exit(8, f'YubiKey - {err}')
except ValueError as err:
controlflow.system_error_exit(9, f'YubiKey - {err}')
def sign(self, message):
if 'mplock' in globals():
mplock.acquire()
try:
conn, _, _ = connect_to_device(self.serial_number)
session = PivSession(conn)
if self.pin:
try:
session.verify_pin(self.pin)
except InvalidPinError as err:
controlflow.system_error_exit(7, f'YubiKey - {err}')
try:
signed = session.sign(slot=self.slot,
key_type=self.key_type,
message=message,
hash_algorithm=hashes.SHA256(),
padding=padding.PKCS1v15())
except ApduError as err:
controlflow.system_error_exit(8, f'YubiKey = {err}')
except ValueError as err:
controlflow.system_error_exit(9, f'YubiKey - {err}')
if 'mplock' in globals():
mplock.release()
return signed

View File

@@ -66,7 +66,7 @@ def csv_field_error_exit(field_name, field_names):
def invalid_json_exit(file_name):
"""Raises a sysyem exit when invalid JSON content is encountered."""
"""Raises a system exit when invalid JSON content is encountered."""
system_error_exit(17, MESSAGE_INVALID_JSON.format(file_name))

View File

@@ -154,39 +154,66 @@ def write_csv_file(csvRows, titles, list_type, todrive):
return True
return False
if GC_Values[GC_CSV_ROW_FILTER]:
for column, filterVal in iter(GC_Values[GC_CSV_ROW_FILTER].items()):
if column not in titles:
sys.stderr.write(
f'WARNING: Row filter column "{column}" is not in output columns\n'
)
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':
csvRows = [
row for row in csvRows if rowCountFilterMatch(
row.get(column, 0), filterVal[1], filterVal[2])
]
else: #boolean
csvRows = [
row for row in csvRows if rowBooleanFilterMatch(
row.get(column, False), filterVal[1])
]
def rowFilterMatch(filters, columns, row):
for c, filterVal in iter(filters.items()):
for column in columns[c]:
if filterVal[1] == 'regex':
if filterVal[2].search(str(row.get(column, ''))):
return True
elif filterVal[1] == 'notregex':
if not filterVal[2].search(str(row.get(column, ''))):
return True
elif filterVal[1] in ['date', 'time']:
if rowDateTimeFilterMatch(
filterVal[1] == 'date', row.get(column, ''),
filterVal[2], filterVal[3]):
return True
elif filterVal[1] == 'count':
if rowCountFilterMatch(
row.get(column, 0), filterVal[2], filterVal[3]):
return True
else: #boolean
if rowBooleanFilterMatch(
row.get(column, False), filterVal[2]):
return True
return False
if GC_Values[GC_CSV_ROW_FILTER] or GC_Values[GC_CSV_ROW_DROP_FILTER]:
if GC_Values[GC_CSV_ROW_FILTER]:
keepColumns = {}
for column, filterVal in iter(GC_Values[GC_CSV_ROW_FILTER].items()):
columns = [t for t in titles if filterVal[0].match(t)]
if columns:
keepColumns[column] = columns
else:
keepColumns[column] = [None]
sys.stderr.write(
f'WARNING: Row filter column pattern "{column}" does not match any output columns\n'
)
else:
keepColumns = None
if GC_Values[GC_CSV_ROW_DROP_FILTER]:
dropColumns = {}
for column, filterVal in iter(GC_Values[GC_CSV_ROW_DROP_FILTER].items()):
columns = [t for t in titles if filterVal[0].match(t)]
if columns:
dropColumns[column] = columns
else:
dropColumns[column] = [None]
sys.stderr.write(
f'WARNING: Row drop filter column pattern "{column}" does not match any output columns\n'
)
else:
dropColumns = None
rows = []
for row in csvRows:
if (((keepColumns is None) or
rowFilterMatch(GC_Values[GC_CSV_ROW_FILTER], keepColumns, row)) and
((dropColumns is None) or
not rowFilterMatch(GC_Values[GC_CSV_ROW_DROP_FILTER], dropColumns, row))):
rows.append(row)
csvRows = rows
if GC_Values[GC_CSV_HEADER_FILTER] or GC_Values[GC_CSV_HEADER_DROP_FILTER]:
if GC_Values[GC_CSV_HEADER_DROP_FILTER]:
titles = [
@@ -288,7 +315,7 @@ def print_json(object_value, spacing=''):
sys.stdout.write(f' {spacing}{i+1}) ')
print_json(a_value, f' {spacing}')
elif isinstance(object_value, dict):
for key in ['kind', 'etag', 'etags']:
for key in ['kind', 'etag', 'etags', '@type']:
object_value.pop(key, None)
for another_object, another_value in object_value.items():
sys.stdout.write(f' {spacing}{another_object}: ')

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

@@ -8,6 +8,7 @@ from unittest.mock import patch
from gam import SetGlobalVariables
import gam.gapi as gapi
from gam.gapi import errors
import httplib2
def create_http_error(status, reason, message):
@@ -21,10 +22,10 @@ def create_http_error(status, reason, message):
Returns:
googleapiclient.errors.HttpError
"""
response = {
response = httplib2.Response({
'status': status,
'content-type': 'application/json',
}
})
content = {
'error': {
'code': status,

View File

@@ -154,7 +154,11 @@ def delACL():
gapi.call(cal.acl(), 'delete', calendarId=calendarId, ruleId=ruleId)
else:
body = {'role': 'none'}
_getCalendarACLScope(5, body)
i = 4
myarg = sys.argv[i].lower().replace('_', '')
if myarg in CALENDAR_ACL_ROLES_MAP:
i += 1
_getCalendarACLScope(i, body)
print(f'Calendar: {calendarId}, Delete ACL: {formatACLScope(body)}')
gapi.call(cal.acl(),
'insert',
@@ -824,7 +828,7 @@ def _showCalendar(userCalendar, j, jcount):
print(f' Color ID: {userCalendar["colorId"]}, ' \
f'Background Color: {userCalendar["backgroundColor"]}, ' \
f'Foreground Color: {userCalendar["foregroundColor"]}')
print(f' Default Reminders:')
print(' Default Reminders:')
for reminder in userCalendar.get('defaultReminders', []):
print(f' Method: {reminder["method"]}, ' \
f'Minutes: {reminder["minutes"]}')

303
src/gam/gapi/cbcm.py Normal file
View File

@@ -0,0 +1,303 @@
"""Chrome Browser Cloud Management API calls"""
import csv
import os.path
import sys
import gam
from gam.var import *
from gam import controlflow
from gam import display
from gam import fileutils
from gam import gapi
from gam.gapi.directory import orgunits as gapi_directory_orgunits
from gam import utils
def _get_customerid():
''' returns customer id without C prefix'''
customer_id = GC_Values[GC_CUSTOMER_ID]
if customer_id[0] == 'C':
customer_id = customer_id[1:]
return customer_id
def build():
return gam.buildGAPIObject('cbcm')
def delete():
cbcm = build()
device_id = sys.argv[3]
customer_id = _get_customerid()
gapi.call(cbcm.chromebrowsers(), 'delete', deviceId=device_id,
customer=customer_id)
print(f'Deleted browser {device_id}')
def info():
cbcm = build()
device_id = sys.argv[3]
projection = 'BASIC'
fields = None
customer_id = _get_customerid()
i = 4
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['basic', 'full']:
projection = myarg.upper()
i += 1
elif myarg == 'fields':
fields = sys.argv[i+1]
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam info browser')
browser = gapi.call(cbcm.chromebrowsers(), 'get',
customer=customer_id,
fields=fields, deviceId=device_id,
projection=projection)
display.print_json(browser)
def move():
cbcm = build()
body = {'resource_ids': []}
customer_id = _get_customerid()
i = 3
resource_ids = []
batch_size = 600
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'ids':
resource_ids.extend(sys.argv[i + 1].split(','))
i += 2
elif myarg == 'query':
query = sys.argv[i + 1]
page_message = gapi.got_total_items_msg('Browsers', '...\n')
browsers = gapi.get_all_pages(cbcm.chromebrowsers(), 'list',
'browsers', page_message=page_message,
customer=customer_id,
query=query, projection='BASIC',
fields='browsers(deviceId),nextPageToken')
ids = [browser['deviceId'] for browser in browsers]
resource_ids.extend(ids)
i += 2
elif myarg == 'file':
with fileutils.open_file(sys.argv[i+1], strip_utf_bom=True) as filed:
for row in filed:
rid = row.strip()
if rid:
resource_ids.append(rid)
i += 2
elif myarg == 'csvfile':
drive, fname_column = os.path.splitdrive(sys.argv[i+1])
if fname_column.find(':') == -1:
controlflow.system_error_exit(
2, 'Expected csvfile FileName:FieldName')
(filename, column) = fname_column.split(':')
with fileutils.open_file(drive + filename) as filed:
input_file = csv.DictReader(filed, restval='')
if column not in input_file.fieldnames:
controlflow.csv_field_error_exit(column,
input_file.fieldnames)
for row in input_file:
rid = row[column].strip()
if rid:
resource_ids.append(rid)
i += 2
elif myarg in ['ou', 'orgunit', 'org']:
org_unit = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1])
body['org_unit_path'] = org_unit
i += 2
elif myarg == 'batchsize':
batch_size = int(sys.argv[i+1])
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam move browsers')
if 'org_unit_path' not in body:
controlflow.missing_argument_exit('ou', 'gam move browsers')
elif not resource_ids:
controlflow.missing_argument_exit('query or ids',
'gam move browsers')
# split moves into max 600 devices per batch
for chunk in range(0, len(resource_ids), batch_size):
body['resource_ids'] = resource_ids[chunk:chunk + batch_size]
print(f' moving {len(body["resource_ids"])} browsers to ' \
f'{body["org_unit_path"]}')
gapi.call(cbcm.chromebrowsers(), 'moveChromeBrowsersToOu',
customer=customer_id, body=body)
def print_():
cbcm = build()
customer_id = _get_customerid()
projection = 'BASIC'
orgUnitPath = query = None
fields = None
titles = []
csv_rows = []
todrive = False
sort_headers = False
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'query':
query = sys.argv[i+1]
i += 2
elif myarg in ['ou', 'org', 'orgunit']:
orgUnitPath = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1], pathOnly=True, absolutePath=True)
i += 2
elif myarg == 'projection':
projection = sys.argv[i + 1].upper()
i += 2
elif myarg == 'todrive':
todrive = True
i += 1
elif myarg == 'sortheaders':
sort_headers = True
i += 1
elif myarg == 'fields':
fields = sys.argv[i + 1].replace(',', ' ').split()
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam print browsers')
if fields:
fields.append('deviceId')
fields = f'browsers({",".join(set(fields))}),nextPageToken'
page_message = gapi.got_total_items_msg('Browsers', '...\n')
browsers = gapi.get_all_pages(cbcm.chromebrowsers(), 'list',
'browsers', page_message=page_message,
customer=customer_id,
orgUnitPath=orgUnitPath, query=query, projection=projection,
fields=fields)
for browser in browsers:
browser = utils.flatten_json(browser)
for a_key in browser:
if a_key not in titles:
titles.append(a_key)
csv_rows.append(browser)
if sort_headers:
display.sort_csv_titles(['deviceId',], titles)
display.write_csv_file(csv_rows, titles, 'Browsers', todrive)
attributes = {
'assetid': 'annotatedAssetId',
'location': 'annotatedLocation',
'notes': 'annotatedNotes',
'user': 'annotatedUser'
}
attribute_fields = ','.join(list(attributes.values()))
def update():
cbcm = build()
customer_id = _get_customerid()
device_id = sys.argv[3]
body = {'deviceId': device_id}
i = 4
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg in attributes:
body[attributes[myarg]] = sys.argv[i+1]
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam update browser')
browser = gapi.call(cbcm.chromebrowsers(), 'get', deviceId=device_id,
customer=customer_id,
projection='BASIC', fields=attribute_fields)
browser.update(body)
result = gapi.call(cbcm.chromebrowsers(), 'update', deviceId=device_id,
customer=customer_id, body=browser,
projection='BASIC', fields="deviceId")
print(f'Updated browser {result["deviceId"]}')
def createtoken():
cbcm = build()
customer_id = _get_customerid()
body = {'token_type': 'CHROME_BROWSER'}
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['ou', 'orgunit', 'org']:
body['org_unit_path'] = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1])
i += 2
elif myarg in ['expire', 'expires']:
body['expire_time'] = utils.get_time_or_delta_from_now(sys.argv[i + 1])
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam create browsertoken')
browser = gapi.call(cbcm.enrollmentTokens(), 'create',
customer=customer_id, body=body)
print(f'Created browser enrollment token {browser["token"]}')
def revoketoken():
cbcm = build()
customer_id = _get_customerid()
token_permanent_id = sys.argv[3]
gapi.call(cbcm.enrollmentTokens(), 'revoke', tokenPermanentId=token_permanent_id,
customer=customer_id)
print(f'Deleted browser enrollment token {token_permanent_id}')
def printshowtokens(csvFormat):
cbcm = build()
customer_id = _get_customerid()
query = None
fields = None
if csvFormat:
titles = ['token']
csv_rows = []
todrive = False
sort_headers = False
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'query':
query = sys.argv[i+1]
i += 2
elif csvFormat and myarg == 'todrive':
todrive = True
i += 1
elif csvFormat and myarg == 'sortheaders':
sort_headers = True
i += 1
elif myarg == 'fields':
fields = sys.argv[i + 1].replace(',', ' ').split()
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
f"gam {['show', 'print'][csvFormat]} browsertokens")
if fields:
fields.append('token')
fields = f'chromeEnrollmentTokens({",".join(set(fields))}),nextPageToken'
page_message = gapi.got_total_items_msg('Chrome Browser Enrollment Tokens', '...\n')
browsers = gapi.get_all_pages(cbcm.enrollmentTokens(), 'list',
'chromeEnrollmentTokens', page_message=page_message,
customer=customer_id,
query=query, fields=fields)
if not csvFormat:
count = len(browsers)
print(f'Show {count} Chrome Browser Enrollment Tokens')
i = 0
for browser in browsers:
i += 1
print(f' Chrome Browser Enrollment Token: {browser["token"]}{gam.currentCount(i, count)}')
browser.pop('kind', None)
for field in browser:
print(f' {field}: {browser[field]}')
else:
for browser in browsers:
browser = utils.flatten_json(browser)
for a_key in browser:
if a_key not in titles:
titles.append(a_key)
csv_rows.append(browser)
if sort_headers:
display.sort_csv_titles(['token',], titles)
display.write_csv_file(csv_rows, titles, 'Chrome Browser Enrollment Tokens', todrive)

207
src/gam/gapi/chat.py Normal file
View File

@@ -0,0 +1,207 @@
import sys
import googleapiclient.errors
import gam
from gam.var import *
from gam import controlflow
from gam import display
from gam import fileutils
from gam import gapi
from gam import utils
from gam.gapi import errors as gapi_errors
# Chat scope isn't in discovery doc so need to manually set
CHAT_SCOPES = ['https://www.googleapis.com/auth/chat.bot']
def build():
return gam.buildGAPIServiceObject('chat',
act_as=None,
scopes=CHAT_SCOPES)
THROW_REASONS = [
gapi_errors.ErrorReason.FOUR_O_FOUR, # Chat API not configured
]
def _chat_error_handler(chat, err):
if err.status_code == 404:
project_id = chat._http.credentials.project_id
url = f'https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat?project={project_id}'
print('ERROR: you need to configure Google Chat for your API project. Please go to:')
print()
print(f' {url}')
print()
print('and complete all fields.')
else:
raise err
sys.exit(1)
def print_spaces():
chat = build()
todrive = False
i =3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(myarg, 'gam print chatspaces')
try:
spaces = gapi.get_all_pages(chat.spaces(), 'list', 'spaces', throw_reasons=THROW_REASONS)
except googleapiclient.errors.HttpError as err:
_chat_error_handler(chat, err)
if not spaces:
print('Bot not added to any Chat rooms or users yet.')
else:
display.write_csv_file(spaces, spaces[0].keys(), 'Chat Spaces', todrive)
def print_members():
chat = build()
space = None
todrive = False
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'space':
space = sys.argv[i+1]
if space[:7] != 'spaces/':
space = f'spaces/{space}'
i += 2
elif myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(myarg, "gam print chatmembers")
if not space:
controlflow.system_error_exit(2,
'space <ChatSpace> is required.')
try:
results = gapi.get_all_pages(chat.spaces().members(), 'list', 'memberships', parent=space)
except googleapiclient.errors.HttpError as err:
_chat_error_handler(chat, err)
members = []
titles = []
for result in results:
member = utils.flatten_json(result)
for key in member:
if key not in titles:
titles.append(key)
members.append(utils.flatten_json(result))
display.write_csv_file(members, titles, 'Chat Members', todrive)
def create_message():
chat = build()
body = {}
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'text':
body['text'] = sys.argv[i+1].replace('\\r', '\r').replace('\\n', '\n')
i += 2
elif myarg == 'textfile':
filename = sys.argv[i + 1]
i, encoding = gam.getCharSet(i + 2)
body['text'] = fileutils.read_file(filename, encoding=encoding)
elif myarg == 'space':
space = sys.argv[i+1]
if space[:7] != 'spaces/':
space = f'spaces/{space}'
i += 2
elif myarg == 'thread':
body['thread'] = {'name': sys.argv[i+1]}
i += 2
else:
controlflow.invalid_argument_exit(myarg, "gam create chat")
if not space:
controlflow.system_error_exit(2,
'space <ChatSpace> is required.')
if 'text' not in body:
controlflow.system_error_exit(2,
'text <String> or textfile <FileName> is required.')
if len(body['text']) > 4096:
body['text'] = body['text'][:4095]
print('WARNING: trimmed message longer than 4k to be 4k in length.')
try:
resp = gapi.call(chat.spaces().messages(),
'create',
parent=space,
body=body,
throw_reasons=THROW_REASONS)
except googleapiclient.errors.HttpError as err:
_chat_error_handler(chat, err)
if 'thread' in body:
print(f'responded to thread {resp["thread"]["name"]}')
else:
print(f'started new thread {resp["thread"]["name"]}')
print(f'message {resp["name"]}')
def delete_message():
chat = build()
name = None
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'name':
name = sys.argv[i+1]
i += 2
else:
controlflow.invalid_argument_exit(myarg, "gam delete chat")
if not name:
controlflow.system_error_exit(2,
'name <String> is required.')
try:
gapi.call(chat.spaces().messages(),
'delete',
name=name)
except googleapiclient.errors.HttpError as err:
_chat_error_handler(chat, err)
def update_message():
chat = build()
body = {}
name = None
updateMask = 'text'
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'text':
body['text'] = sys.argv[i+1].replace('\\r', '\r').replace('\\n', '\n')
i += 2
elif myarg == 'textfile':
filename = sys.argv[i + 1]
i, encoding = gam.getCharSet(i + 2)
body['text'] = fileutils.read_file(filename, encoding=encoding)
elif myarg == 'name':
name = sys.argv[i+1]
i += 2
else:
controlflow.invalid_argument_exit(myarg, "gam update chat")
if not name:
controlflow.system_error_exit(2,
'name <String> is required.')
if 'text' not in body:
controlflow.system_error_exit(2,
'text <String> or textfile <FileName> is required.')
if len(body['text']) > 4096:
body['text'] = body['text'][:4095]
print('WARNING: trimmed message longer than 4k to be 4k in length.')
try:
resp = gapi.call(chat.spaces().messages(),
'update',
name=name,
updateMask=updateMask,
body=body)
except googleapiclient.errors.HttpError as err:
_chat_error_handler(chat, err)
if 'thread' in body:
print(f'updated response to thread {resp["thread"]["name"]}')
else:
print(f'updated message on thread {resp["thread"]["name"]}')
print(f'message {resp["name"]}')

View File

@@ -0,0 +1,229 @@
"""Chrome Version History API calls"""
import re
import sys
import gam
from gam.var import *
from gam import controlflow
from gam import display
from gam import gapi
from gam import utils
def build():
return gam.buildGAPIObjectNoAuthentication('versionhistory')
CHROME_HISTORY_ENTITY_CHOICES = {
'platforms',
'channels',
'versions',
'releases',
}
CHROME_VERSIONHISTORY_ORDERBY_CHOICE_MAP = {
'versions': {
'channel': 'channel',
'name': 'name',
'platform': 'platform',
'version': 'version'
},
'releases': {
'channel': 'channel',
'endtime': 'endtime',
'fraction': 'fraction',
'name': 'name',
'platform': 'platform',
'starttime': 'starttime',
'version': 'version'
}
}
CHROME_VERSIONHISTORY_TITLES = {
'platforms': ['platform'],
'channels': ['channel', 'platform'],
'versions': ['version', 'channel', 'platform',
'major_version', 'minor_version', 'build', 'patch'],
'releases': ['version', 'channel', 'platform',
'major_version', 'minor_version', 'build', 'patch',
'fraction', 'serving.startTime','serving.endTime']
}
def get_relative_milestone(channel='stable', minus=0):
'''
takes a channel and minus like stable and -1.
returns current given milestone number
'''
cv = build()
parent = f'chrome/platforms/all/channels/{channel}/versions/all'
releases = gapi.get_all_pages(cv.platforms().channels().versions().releases(),
'list',
'releases',
parent=parent,
fields='releases/version,nextPageToken')
milestones = []
# Note that milestones are usually sequential but some numbers
# may be skipped. For example, there was no Chrome 82 stable.
# Thus we need to do more than find the latest version and subtract.
for release in releases:
milestone = release.get('version').split('.')[0]
if milestone not in milestones:
milestones.append(milestone)
milestones.sort(reverse=True)
try:
return milestones[minus]
except IndexError:
return ''
def get_platform_map(cv=None):
'''returns dict mapping of platform choices'''
if cv is None:
cv = build()
result = gapi.get_all_pages(cv.platforms(),
'list',
'platforms',
parent='chrome')
platforms = [p.get('platformType', '').lower() for p in result]
platform_map = {'all': 'all'}
for cplatform in platforms:
key = cplatform.replace('_', '')
platform_map[key] = cplatform
return platform_map
def get_channel_map(cv=None):
'''returns dict mapping of channel choices'''
if cv is None:
cv = build()
result = gapi.get_all_pages(cv.platforms().channels(),
'list',
'channels',
parent='chrome/platforms/all')
channels = [c.get('channelType', '').lower() for c in result]
channels = list(set(channels))
channel_map = {'all': 'all'}
for channel in channels:
key = channel.replace('_', '')
channel_map[key] = channel
return channel_map
def printHistory():
cv = build()
entityType = sys.argv[3].lower().replace('_', '')
if entityType not in CHROME_HISTORY_ENTITY_CHOICES:
msg = f'{entityType} is not a valid argument to "gam print chromehistory"'
controlflow.system_error_exit(3, msg)
todrive = False
csvRows = []
cplatform = 'all'
channel = 'all'
version = 'all'
kwargs = {}
orderByList = []
i = 4
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'todrive':
todrive = True
i += 1
elif entityType != 'platforms' and myarg == 'platform':
cplatform = sys.argv[i + 1].lower().replace('_', '')
platform_map = get_platform_map(cv)
if cplatform not in platform_map:
controlflow.expected_argument_exit('platform',
', '.join(platform_map),
cplatform)
cplatform = platform_map[cplatform]
i += 2
elif entityType in {'versions', 'releases'} and myarg == 'channel':
channel = sys.argv[i + 1].lower().replace('_', '')
channel_map = get_channel_map(cv)
if channel not in channel_map:
controlflow.expected_argument_exit('channel',
', '.join(channel_map),
channel)
channel = channel_map[channel]
i += 2
elif entityType == 'releases' and myarg == 'version':
version = sys.argv[i + 1]
i += 2
elif entityType in {'versions', 'releases'} and myarg == 'orderby':
fieldName = sys.argv[i + 1].lower().replace('_', '')
i += 2
if fieldName in CHROME_VERSIONHISTORY_ORDERBY_CHOICE_MAP[entityType]:
fieldName = CHROME_VERSIONHISTORY_ORDERBY_CHOICE_MAP[entityType][fieldName]
orderBy = ''
if i < len(sys.argv):
orderBy = sys.argv[i].lower()
if orderBy in SORTORDER_CHOICES_MAP:
orderBy = SORTORDER_CHOICES_MAP[orderBy]
i += 1
if orderBy != 'DESCENDING':
orderByList.append(fieldName)
else:
orderByList.append(f'{fieldName} desc')
else:
controlflow.expected_argument_exit('orderby',
', '.join(CHROME_VERSIONHISTORY_ORDERBY_CHOICE_MAP[entityType]),
fieldName)
elif entityType in {'versions', 'releases'} and myarg == 'filter':
kwargs['filter'] = sys.argv[i + 1]
i += 2
else:
msg = f'{myarg} is not a valid argument to "gam print chromehistory {entityType}"'
controlflow.system_error_exit(3, msg)
if orderByList:
kwargs['orderBy'] = ','.join(orderByList)
if entityType == 'platforms':
svc = cv.platforms()
parent = 'chrome'
elif entityType == 'channels':
svc = cv.platforms().channels()
parent = f'chrome/platforms/{cplatform}'
elif entityType == 'versions':
svc = cv.platforms().channels().versions()
parent = f'chrome/platforms/{cplatform}/channels/{channel}'
else: #elif entityType == 'releases'
svc = cv.platforms().channels().versions().releases()
parent = f'chrome/platforms/{cplatform}/channels/{channel}/versions/{version}'
reportTitle = f'Chrome Version History {entityType.capitalize()}'
page_message = gapi.got_total_items_msg(reportTitle, '...\n')
gam.printGettingAllItems(reportTitle, None)
citems = gapi.get_all_pages(svc, 'list', entityType,
page_message=page_message,
parent=parent,
fields=f'nextPageToken,{entityType}',
**kwargs)
for citem in citems:
for key in list(citem):
if key.endswith('Type'):
newkey = key[:-4]
citem[newkey] = citem.pop(key)
if 'channel' in citem:
citem['channel'] = citem['channel'].lower()
else:
channel_match = re.search(r"\/channels\/([^/]*)", citem['name'])
if channel_match:
try:
citem['channel'] = channel_match.group(1)
except IndexError:
pass
if 'platform' in citem:
citem['platform'] = citem['platform'].lower()
else:
platform_match = re.search(r"\/platforms\/([^/]*)", citem['name'])
if platform_match:
try:
citem['platform'] = platform_match.group(1)
except IndexError:
pass
if citem.get('version', '').count('.') == 3:
citem['major_version'], \
citem['minor_version'], \
citem['build'], \
citem['patch'] = citem['version'].split('.')
citem.pop('name')
csvRows.append(utils.flatten_json(citem))
display.write_csv_file(csvRows, CHROME_VERSIONHISTORY_TITLES[entityType], reportTitle, todrive)

View File

@@ -0,0 +1,265 @@
"""Chrome Management API calls"""
import sys
import gam
from gam.var import GC_CUSTOMER_ID, GC_Values, MY_CUSTOMER
from gam.var import CROS_START_ARGUMENTS, CROS_END_ARGUMENTS
from gam.var import YYYYMMDD_FORMAT
from gam import controlflow
from gam import display
from gam import gapi
from gam.gapi.directory import orgunits as gapi_directory_orgunits
from gam.gapi.directory.cros import _getFilterDate
def _get_customerid():
customer = GC_Values[GC_CUSTOMER_ID]
if customer != MY_CUSTOMER and customer[0] != 'C':
customer = 'C' + customer
return f'customers/{customer}'
def _get_orgunit(orgunit):
if orgunit.startswith('orgunits/'):
return orgunit
_, orgunitid = gapi_directory_orgunits.getOrgUnitId(orgunit)
return f'{orgunitid[3:]}'
def build():
return gam.buildGAPIObject('chromemanagement')
CHROME_APPS_ORDERBY_CHOICE_MAP = {
'appname': 'app_name',
'apptype': 'appType',
'installtype': 'install_type',
'numberofpermissions': 'number_of_permissions',
'totalinstallcount': 'total_install_count',
}
CHROME_APPS_TITLES = [
'appId', 'displayName',
'browserDeviceCount', 'osUserCount',
'appType', 'description',
'appInstallType', 'appSource',
'disabled', 'homepageUri',
'permissions'
]
def printApps():
cm = build()
customer = _get_customerid()
todrive = False
titles = CHROME_APPS_TITLES
csvRows = []
orgunit = None
pfilter = None
orderBy = None
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'todrive':
todrive = True
i += 1
elif myarg in ['ou', 'org', 'orgunit']:
orgunit = _get_orgunit(sys.argv[i+1])
i += 2
elif myarg == 'filter':
pfilter = sys.argv[i + 1]
i += 2
elif myarg == 'orderby':
orderBy = sys.argv[i + 1].lower().replace('_', '')
if orderBy not in CHROME_APPS_ORDERBY_CHOICE_MAP:
controlflow.expected_argument_exit('orderby',
', '.join(CHROME_APPS_ORDERBY_CHOICE_MAP),
orderBy)
orderBy = CHROME_APPS_ORDERBY_CHOICE_MAP[orderBy]
i += 2
else:
msg = f'{myarg} is not a valid argument to "gam print chromeapps"'
controlflow.system_error_exit(3, msg)
if orgunit:
orgUnitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit, None)
titles.append('orgUnitPath')
else:
orgUnitPath = '/'
gam.printGettingAllItems('Chrome Installed Applications', pfilter)
page_message = gapi.got_total_items_msg('Chrome Installed Applications', '...\n')
apps = gapi.get_all_pages(cm.customers().reports(),
'countInstalledApps',
'installedApps',
page_message=page_message,
customer=customer, orgUnitId=orgunit,
filter=pfilter, orderBy=orderBy)
for app in apps:
if orgunit:
app['orgUnitPath'] = orgUnitPath
if 'permissions'in app:
app['permissions'] = ' '.join(app['permissions'])
csvRows.append(app)
display.write_csv_file(csvRows, titles, 'Chrome Installed Applications', todrive)
CHROME_APP_DEVICES_APPTYPE_CHOICE_MAP = {
'extension': 'EXTENSION',
'app': 'APP',
'theme': 'THEME',
'hostedapp': 'HOSTED_APP',
'androidapp': 'ANDROID_APP',
}
CHROME_APP_DEVICES_ORDERBY_CHOICE_MAP = {
'deviceid': 'deviceId',
'machine': 'machine',
}
CHROME_APP_DEVICES_TITLES = [
'appId', 'appType', 'deviceId', 'machine'
]
def printAppDevices():
cm = build()
customer = _get_customerid()
todrive = False
titles = CHROME_APP_DEVICES_TITLES
csvRows = []
orgunit = None
appId = None
appType = None
startDate = None
endDate = None
pfilter = None
orderBy = None
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'todrive':
todrive = True
i += 1
elif myarg in ['ou', 'org', 'orgunit']:
orgunit = _get_orgunit(sys.argv[i+1])
i += 2
elif myarg == 'appid':
appId = sys.argv[i + 1]
i += 2
elif myarg == 'apptype':
appType = sys.argv[i + 1].lower().replace('_', '')
if appType not in CHROME_APP_DEVICES_APPTYPE_CHOICE_MAP:
controlflow.expected_argument_exit('orderby',
', '.join(CHROME_APP_DEVICES_APPTYPE_CHOICE_MAP),
appType)
appType = CHROME_APP_DEVICES_APPTYPE_CHOICE_MAP[appType]
i += 2
elif myarg in CROS_START_ARGUMENTS:
startDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT)
i += 2
elif myarg in CROS_END_ARGUMENTS:
endDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT)
i += 2
elif myarg == 'orderby':
orderBy = sys.argv[i + 1].lower().replace('_', '')
if orderBy not in CHROME_APP_DEVICES_ORDERBY_CHOICE_MAP:
controlflow.expected_argument_exit('orderby',
', '.join(CHROME_APP_DEVICES_ORDERBY_CHOICE_MAP),
orderBy)
orderBy = CHROME_APP_DEVICES_ORDERBY_CHOICE_MAP[orderBy]
i += 2
else:
msg = f'{myarg} is not a valid argument to "gam print chromeappdevices"'
controlflow.system_error_exit(3, msg)
if not appId:
controlflow.system_error_exit(3, 'You must specify an appid')
if not appType:
controlflow.system_error_exit(3, 'You must specify an apptype')
if endDate:
pfilter = f'last_active_date<={endDate}'
if startDate:
if pfilter:
pfilter += ' AND '
else:
pfilter = ''
pfilter += f'last_active_date>={startDate}'
if orgunit:
orgUnitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit, None)
titles.append('orgUnitPath')
else:
orgUnitPath = '/'
gam.printGettingAllItems('Chrome Installed Application Devices', pfilter)
page_message = gapi.got_total_items_msg('Chrome Installed Application Devices', '...\n')
devices = gapi.get_all_pages(cm.customers().reports(),
'findInstalledAppDevices',
'devices',
page_message=page_message,
appId=appId, appType=appType,
customer=customer, orgUnitId=orgunit,
filter=pfilter, orderBy=orderBy)
for device in devices:
if orgunit:
device['orgUnitPath'] = orgUnitPath
device['appId'] = appId
device['appType'] = appType
csvRows.append(device)
display.write_csv_file(csvRows, titles, 'Chrome Installed Application Devices', todrive)
CHROME_VERSIONS_TITLES = [
'version', 'count', 'channel', 'deviceOsVersion', 'system'
]
def printVersions():
cm = build()
customer = _get_customerid()
todrive = False
titles = CHROME_VERSIONS_TITLES
csvRows = []
orgunit = None
startDate = None
endDate = None
pfilter = None
reverse = False
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'todrive':
todrive = True
i += 1
elif myarg in ['ou', 'org', 'orgunit']:
orgunit = _get_orgunit(sys.argv[i+1])
i += 2
elif myarg in CROS_START_ARGUMENTS:
startDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT)
i += 2
elif myarg in CROS_END_ARGUMENTS:
endDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT)
i += 2
elif myarg == 'recentfirst':
reverse = True
i += 1
else:
msg = f'{myarg} is not a valid argument to "gam print chromeversions"'
controlflow.system_error_exit(3, msg)
if endDate:
pfilter = f'last_active_date<={endDate}'
if startDate:
if pfilter:
pfilter += ' AND '
else:
pfilter = ''
pfilter += f'last_active_date>={startDate}'
if orgunit:
orgUnitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit, None)
titles.append('orgUnitPath')
else:
orgUnitPath = '/'
gam.printGettingAllItems('Chrome Versions', pfilter)
page_message = gapi.got_total_items_msg('Chrome Versions', '...\n')
versions = gapi.get_all_pages(cm.customers().reports(),
'countChromeVersions',
'browserVersions',
page_message=page_message,
customer=customer, orgUnitId=orgunit, filter=pfilter)
for version in sorted(versions, key=lambda k: k.get('version', 'Unknown'), reverse=reverse):
if orgunit:
version['orgUnitPath'] = orgUnitPath
if 'version' not in version:
version['version'] = 'Unknown'
csvRows.append(version)
display.write_csv_file(csvRows, titles, 'Chrome Versions', todrive)

View File

@@ -0,0 +1,382 @@
"""Chrome Browser Cloud Management API calls"""
import re
import sys
import googleapiclient.errors
import gam
from gam.var import GC_CUSTOMER_ID, GC_Values, MY_CUSTOMER
from gam import controlflow
from gam import gapi
from gam.gapi import errors as gapi_errors
from gam.gapi import chromehistory as gapi_chromehistory
from gam.gapi.directory import orgunits as gapi_directory_orgunits
from gam import utils
def _get_customerid():
customer = GC_Values[GC_CUSTOMER_ID]
if customer != MY_CUSTOMER and customer[0] != 'C':
customer = 'C' + customer
return f'customers/{customer}'
def _get_orgunit(orgunit):
if orgunit.startswith('orgunits/'):
return orgunit
_, orgunitid = gapi_directory_orgunits.getOrgUnitId(orgunit)
return f'orgunits/{orgunitid[3:]}'
def build():
return gam.buildGAPIObject('chromepolicy')
def printshow_policies():
svc = build()
customer = _get_customerid()
orgunit = None
printer_id = None
app_id = None
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['ou', 'org', 'orgunit']:
orgunit = _get_orgunit(sys.argv[i+1])
i += 2
elif myarg == 'printerid':
printer_id = sys.argv[i+1]
i += 2
elif myarg == 'appid':
app_id = sys.argv[i+1]
i += 2
else:
msg = f'{myarg} is not a valid argument to "gam print chromepolicy"'
controlflow.system_error_exit(3, msg)
if not orgunit:
controlflow.system_error_exit(3, 'You must specify an orgunit')
body = {
'policyTargetKey': {
'targetResource': orgunit,
}
}
if printer_id:
body['policyTargetKey']['additionalTargetKeys'] = {'printer_id': printer_id}
namespaces = ['chrome.printers']
elif app_id:
body['policyTargetKey']['additionalTargetKeys'] = {'app_id': app_id}
namespaces = ['chrome.users.apps',
'chrome.devices.managedGuest.apps',
'chrome.devices.kiosk.apps']
else:
namespaces = [
'chrome.users',
# Not yet implemented:
# 'chrome.devices',
# 'chrome.devices.managedGuest',
# 'chrome.devices.kiosk',
]
throw_reasons = [gapi_errors.ErrorReason.FOUR_O_O,]
orgunitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit[9:], None)
header = f'Organizational Unit: {orgunitPath}'
if printer_id:
header += f', printerid: {printer_id}'
elif app_id:
header += f', appid: {app_id}'
print(header)
for namespace in namespaces:
body['policySchemaFilter'] = f'{namespace}.*'
try:
policies = gapi.get_all_pages(svc.customers().policies(), 'resolve',
items='resolvedPolicies',
throw_reasons=throw_reasons,
customer=customer,
body=body)
except googleapiclient.errors.HttpError:
policies = []
for policy in sorted(policies, key=lambda k: k.get('value', {}).get('policySchema', '')):
print()
name = policy.get('value', {}).get('policySchema', '')
schema = CHROME_SCHEMA_TYPE_MESSAGE.get(name)
print(name)
values = policy.get('value', {}).get('value', {})
for setting, value in values.items():
# Handle TYPE_MESSAGE fields with durations or counts as a special case
if schema and setting == schema['casedField']:
value = value.get(schema['type'], '')
if value:
if value.endswith('s'):
value = value[:-1]
value = int(value) // schema['scale']
elif isinstance(value, str) and value.find('_ENUM_') != -1:
value = value.split('_ENUM_')[-1]
print(f' {setting}: {value}')
def build_schemas(svc=None, sfilter=None):
if not svc:
svc = build()
parent = _get_customerid()
schemas = gapi.get_all_pages(svc.customers().policySchemas(), 'list',
items='policySchemas', parent=parent, filter=sfilter)
schema_objects = {}
for schema in schemas:
schema_name = schema.get('name', '').split('/')[-1]
schema_dict = {
'name': schema_name,
'description': schema.get('policyDescription', ''),
'settings': {},
}
field_descriptions = schema.get('fieldDescriptions', [])
for mtype in schema.get('definition', {}).get('messageType', {}):
for setting in mtype.get('field', {}):
setting_name = setting.get('name', '')
setting_dict = {
'name': setting_name,
'constraints': None,
'descriptions': [],
'type': setting.get('type'),
}
if setting_dict['type'] == 'TYPE_STRING' and \
setting.get('label') == 'LABEL_REPEATED':
setting_dict['type'] = 'TYPE_LIST'
if setting_dict['type'] == 'TYPE_ENUM':
type_name = setting['typeName']
for an_enum in schema['definition']['enumType']:
if an_enum['name'] == type_name:
setting_dict['enums'] = [enum['name'] for enum in an_enum['value']]
setting_dict['enum_prefix'] = utils.commonprefix(setting_dict['enums'])
prefix_len = len(setting_dict['enum_prefix'])
setting_dict['enums'] = [enum[prefix_len:] for enum \
in setting_dict['enums'] \
if not enum.endswith('UNSPECIFIED')]
setting_dict['descriptions'] = ['']*len(setting_dict['enums'])
if field_descriptions:
for i, an in enumerate(setting_dict['enums']):
for fdesc in field_descriptions:
if fdesc.get('field') == setting_name:
for d in fdesc.get('knownValueDescriptions', []):
if d['value'][prefix_len:] == an:
setting_dict['descriptions'][i] = d['description']
break
break
break
elif setting_dict['type'] == 'TYPE_MESSAGE':
continue
else:
setting_dict['enums'] = None
for fdesc in schema.get('fieldDescriptions', []):
if fdesc.get('field') == setting_name:
if 'knownValueDescriptions' in fdesc:
setting_dict['descriptions'] = fdesc['knownValueDescriptions']
elif 'description' in fdesc:
setting_dict['descriptions'] = [fdesc['description']]
schema_dict['settings'][setting_name.lower()] = setting_dict
schema_objects[schema_name.lower()] = schema_dict
return schema_objects
def printshow_schemas():
svc = build()
sfilter = None
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'filter':
sfilter = sys.argv[i+1]
i += 2
else:
msg = f'{myarg} is not a valid argument to "gam print chromeschema"'
controlflow.system_error_exit(3, msg)
schemas = build_schemas(svc, sfilter)
for _, value in sorted(iter(schemas.items())):
print(f'{value.get("name")}: {value.get("description")}')
for val in value['settings'].values():
vtype = val.get('type')
print(f' {val.get("name")}: {vtype}')
if vtype == 'TYPE_ENUM':
enums = val.get('enums', [])
descriptions = val.get('descriptions', [])
for i in range(len(val.get('enums', []))):
print(f' {enums[i]}: {descriptions[i]}')
elif vtype == 'TYPE_BOOL':
pvs = val.get('descriptions')
for pvi in pvs:
if isinstance(pvi, dict):
pvalue = pvi.get('value')
pdescription = pvi.get('description')
print(f' {pvalue}: {pdescription}')
elif isinstance(pvi, list):
print(f' {pvi[0]}')
else:
description = val.get('descriptions')
if len(description) > 0:
print(f' {description[0]}')
print()
def delete_policy():
svc = build()
customer = _get_customerid()
schemas = build_schemas(svc)
orgunit = None
printer_id = None
app_id = None
i = 3
body = {'requests': []}
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['ou', 'org', 'orgunit']:
orgunit = _get_orgunit(sys.argv[i+1])
i += 2
elif myarg == 'printerid':
printer_id = sys.argv[i+1]
i += 2
elif myarg == 'appid':
app_id = sys.argv[i+1]
i += 2
elif myarg in schemas:
body['requests'].append({'policySchema': schemas[myarg]['name']})
i += 1
else:
msg = f'{myarg} is not a valid argument to "gam delete chromepolicy"'
controlflow.system_error_exit(3, msg)
if not orgunit:
controlflow.system_error_exit(3, 'You must specify an orgunit')
for request in body['requests']:
request['policyTargetKey'] = {'targetResource': orgunit}
if printer_id:
request['policyTargetKey']['additionalTargetKeys'] = {'printer_id': printer_id}
elif app_id:
request['policyTargetKey']['additionalTargetKeys'] = {'app_id': app_id}
gapi.call(svc.customers().policies().orgunits(), 'batchInherit', customer=customer, body=body)
CHROME_SCHEMA_TYPE_MESSAGE = {
'chrome.users.SessionLength':
{'field': 'sessiondurationlimit', 'casedField': 'sessionDurationLimit',
'type': 'duration', 'minVal': 1, 'maxVal': 1440, 'scale': 60},
'chrome.users.BrowserSwitcherDelayDuration':
{'field': 'browserswitcherdelayduration', 'casedField': 'browserSwitcherDelayDuration',
'type': 'duration', 'minVal': 0, 'maxVal': 30, 'scale': 1},
'chrome.users.MaxInvalidationFetchDelay':
{'field': 'maxinvalidationfetchdelay', 'casedField': 'maxInvalidationFetchDelay',
'type': 'duration', 'minVal': 1, 'maxVal': 30, 'scale': 1},
'chrome.users.SecurityTokenSessionSettings':
{'field': 'securitytokensessionnotificationseconds', 'casedField': 'securityTokenSessionNotificationSeconds',
'type': 'duration', 'minVal': 0, 'maxVal': 9999, 'scale': 1},
'chrome.users.PrintingMaxSheetsAllowed':
{'field': 'printingmaxsheetsallowednullable', 'casedField': 'printingMaxSheetsAllowedNullable',
'type': 'value', 'minVal': 1, 'maxVal': None, 'scale': 1},
}
def update_policy():
svc = build()
customer = _get_customerid()
schemas = build_schemas(svc)
orgunit = None
printer_id = None
app_id = None
i = 3
body = {'requests': []}
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['ou', 'org', 'orgunit']:
orgunit = _get_orgunit(sys.argv[i+1])
i += 2
elif myarg == 'printerid':
printer_id = sys.argv[i+1]
i += 2
elif myarg == 'appid':
app_id = sys.argv[i+1]
i += 2
elif myarg in schemas:
schemaName = schemas[myarg]['name']
body['requests'].append({'policyValue': {'policySchema': schemaName,
'value': {}},
'updateMask': ''})
i += 1
while i < len(sys.argv):
field = sys.argv[i].lower()
if field in ['ou', 'org', 'orgunit', 'printerid', 'appid'] or '.' in field:
break # field is actually a new policy, orgunit or app/printer id
# Handle TYPE_MESSAGE fields with durations or counts as a special case
schema = CHROME_SCHEMA_TYPE_MESSAGE.get(schemaName)
if schema and field == schema['field']:
casedField = schema['casedField']
value = gam.getInteger(sys.argv[i+1], casedField,
minVal=schema['minVal'], maxVal=schema['maxVal'])*schema['scale']
if schema['type'] == 'duration':
body['requests'][-1]['policyValue']['value'][casedField] = {schema['type']: f'{value}s'}
else:
body['requests'][-1]['policyValue']['value'][casedField] = {schema['type']: value}
body['requests'][-1]['updateMask'] += f'{casedField},'
i += 2
continue
expected_fields = ', '.join(schemas[myarg]['settings'])
if field not in expected_fields:
msg = f'Expected {myarg} field of {expected_fields}. Got {field}.'
controlflow.system_error_exit(4, msg)
cased_field = schemas[myarg]['settings'][field]['name']
value = sys.argv[i+1]
vtype = schemas[myarg]['settings'][field]['type']
if vtype in ['TYPE_INT64', 'TYPE_INT32', 'TYPE_UINT64']:
if not value.isnumeric():
msg = f'Value for {myarg} {field} must be a number, got {value}'
controlflow.system_error_exit(7, msg)
value = int(value)
elif vtype in ['TYPE_BOOL']:
value = gam.getBoolean(value, field)
elif vtype in ['TYPE_ENUM']:
value = value.upper()
prefix = schemas[myarg]['settings'][field]['enum_prefix']
enum_values = schemas[myarg]['settings'][field]['enums']
if value in enum_values:
value = f'{prefix}{value}'
elif value.replace(prefix, '') in enum_values:
pass
else:
expected_enums = ', '.join(enum_values)
msg = f'Expected {myarg} {field} value to be one of ' \
f'{expected_enums}, got {value}'
controlflow.system_error_exit(8, msg)
elif vtype in ['TYPE_LIST']:
value = value.split(',')
if myarg == 'chrome.users.chromebrowserupdates' and \
cased_field == 'targetVersionPrefixSetting':
mg = re.compile(r'^([a-z]+)-(\d+)$').match(value)
if mg:
channel = mg.group(1).lower().replace('_', '')
minus = mg.group(2)
channel_map = gapi_chromehistory.get_channel_map(None)
if channel not in channel_map:
expected_channels = ', '.join(channel_map)
msg = f'Expected {myarg} {cased_field} channel to be one of ' \
f'{expected_channels}, got {channel}'
controlflow.system_error_exit(8, msg)
milestone = gapi_chromehistory.get_relative_milestone(
channel_map[channel], int(minus))
if not milestone:
msg = f'{myarg} {cased_field} channel {channel} offset {minus} does not exist'
controlflow.system_error_exit(8, msg)
value = f'{milestone}.'
body['requests'][-1]['policyValue']['value'][cased_field] = value
body['requests'][-1]['updateMask'] += f'{cased_field},'
i += 2
else:
msg = f'{myarg} is not a valid argument to "gam update chromepolicy"'
controlflow.system_error_exit(4, msg)
if not orgunit:
controlflow.system_error_exit(3, 'You must specify an orgunit')
for request in body['requests']:
request['policyTargetKey'] = {'targetResource': orgunit}
if printer_id:
request['policyTargetKey']['additionalTargetKeys'] = {'printer_id': printer_id}
elif app_id:
request['policyTargetKey']['additionalTargetKeys'] = {'app_id': app_id}
gapi.call(svc.customers().policies().orgunits(),
'batchModify',
customer=customer,
body=body)

View File

@@ -3,6 +3,7 @@ import sys
import googleapiclient
import gam
from gam.var import *
from gam import controlflow
from gam import display
@@ -11,7 +12,7 @@ from gam import gapi
from gam import utils
from gam.gapi import errors as gapi_errors
from gam.gapi import cloudidentity as gapi_cloudidentity
from gam.gapi.directory import customer as gapi_directory_customer
def _get_device_customerid():
customer = GC_Values[GC_CUSTOMER_ID]
@@ -49,6 +50,7 @@ def create():
result = gapi.call(ci.devices(), 'create', customer=customer, body=body)
print(f'Created device {result["response"]["name"]}')
def _get_device_name():
name = sys.argv[3]
if name == 'id':
@@ -65,10 +67,16 @@ def info():
device = gapi.call(ci.devices(), 'get', name=name, customer=customer)
device_users = gapi.get_all_pages(ci.devices().deviceUsers(), 'list',
'deviceUsers', parent=name, customer=customer)
for device_user in device_users:
parent = device_user['name']
device_user['client_states'] = gapi.get_all_pages(
ci.devices().deviceUsers().clientStates(),
'list', 'clientStates', parent=parent, customer=customer)
display.print_json(device)
print('Device Users:')
display.print_json(device_users)
def _generic_action(action, device_user=False):
ci = gapi_cloudidentity.build_dwd()
customer = _get_device_customerid()
@@ -87,30 +95,153 @@ def _generic_action(action, device_user=False):
op = gapi.call(endpoint, action, name=name, **kwargs)
print(op)
def delete():
_generic_action('delete')
def cancel_wipe():
_generic_action('cancelWipe')
def wipe():
_generic_action('wipe')
def approve_user():
_generic_action('approve', True)
def block_user():
_generic_action('block', True)
def cancel_wipe_user():
_generic_action('cancelWipe', True)
def delete_user():
_generic_action('delete', True)
def wipe_user():
_generic_action('wipe', True)
def _get_deviceuser_name():
i = 3
name = sys.argv[i]
if name == 'id':
i += 1
name = sys.argv[i]
if not name.startswith('devices/'):
name = f'devices/{name}'
return (i+1, name)
def info_state():
ci = gapi_cloudidentity.build_dwd()
gapi_directory_customer.setTrueCustomerId()
customer = _get_device_customerid()
customer_id = customer[10:]
client_id = f'{customer_id}-gam'
i, deviceuser = _get_deviceuser_name()
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'clientid':
client_id = f'{customer_id}-{sys.argv[i+1]}'
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam info deviceuserstate')
name = f'{deviceuser}/clientStates/{client_id}'
result = gapi.call(ci.devices().deviceUsers().clientStates(), 'get',
name=name, customer=customer)
display.print_json(result)
def update_state():
ci = gapi_cloudidentity.build_dwd()
gapi_directory_customer.setTrueCustomerId()
customer = _get_device_customerid()
customer_id = customer[10:]
client_id = f'{customer_id}-gam'
body = {}
i, deviceuser = _get_deviceuser_name()
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'clientid':
client_id = f'{customer_id}-{sys.argv[i+1]}'
i += 2
elif myarg in ['assettag', 'assettags']:
body['assetTags'] = gam.shlexSplitList(sys.argv[i+1])
if body['assetTags'] == ['clear']:
# TODO: this doesn't work to clear
# existing values. Figure out why.
body['assetTags'] = [None]
i += 2
elif myarg in ['compliantstate', 'compliancestate']:
comp_states = gapi.get_enum_values_minus_unspecified(
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1ClientState']['properties']['complianceState']['enum'])
body['complianceState'] = sys.argv[i+1].upper()
if body['complianceState'] not in comp_states:
controlflow.expected_argument_exit('compliant_state',
', '.join(comp_states),
sys.argv[i+1])
i += 2
elif myarg == 'customid':
body['customId'] = sys.argv[i+1]
i += 2
elif myarg == 'healthscore':
health_scores = gapi.get_enum_values_minus_unspecified(
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1ClientState']['properties']['healthScore']['enum'])
body['healthScore'] = sys.argv[i+1].upper()
if body['healthScore'] == 'CLEAR':
body['healthScore'] = None
if body['healthScore'] and body['healthScore'] not in health_scores:
controlflow.expected_argument_exit('health_score',
', '.join(health_scores),
sys.argv[i+1])
i += 2
elif myarg == 'customvalue':
allowed_types = ['bool', 'number', 'string']
value_type = sys.argv[i+1].lower()
if value_type not in allowed_types:
controlflow.expected_argument_exit('custom_value',
', '.join(allowed_types),
sys.argv[i+1])
key = sys.argv[i+2]
value = sys.argv[i+3]
if value_type == 'bool':
value = gam.getBoolean(value, key)
elif value_type == 'number':
value = int(value)
body.setdefault('keyValuePairs', {})
body['keyValuePairs'][key] = {f'{value_type}Value': value}
i += 4
elif myarg in ['managedstate']:
managed_states = gapi.get_enum_values_minus_unspecified(
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1ClientState']['properties']['managed']['enum'])
body['managed'] = sys.argv[i+1].upper()
if body['managed'] == 'CLEAR':
body['managed'] = None
if body['managed'] and body['managed'] not in managed_states:
controlflow.expected_argument_exit('managed_state',
', '.join(managed_states),
sys.argv[i+1])
i += 2
elif myarg in ['scorereason']:
body['scoreReason'] = sys.argv[i+1]
if body['scoreReason'] == 'clear':
body['scoreReason'] = None
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam update deviceuserstate')
name = f'{deviceuser}/clientStates/{client_id}'
updateMask = ','.join(body.keys())
result = gapi.call(ci.devices().deviceUsers().clientStates(), 'patch',
name=name, customer=customer, updateMask=updateMask, body=body)
display.print_json(result)
def print_():
ci = gapi_cloudidentity.build_dwd()
customer = _get_device_customerid()

View File

@@ -1,3 +1,7 @@
import sys
import googleapiclient
import gam
from gam.var import *
from gam import controlflow
@@ -10,7 +14,7 @@ from gam.gapi.directory import customer as gapi_directory_customer
def create():
ci = gapi_cloudidentity.build('cloudidentity_beta')
ci = gapi_cloudidentity.build()
initialGroupConfig = 'EMPTY'
gapi_directory_customer.setTrueCustomerId()
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
@@ -40,7 +44,6 @@ def create():
body['additionalGroupKeys'].append({'id': alias})
i += 2
elif myarg in ['dynamic']:
# As of 2020/06/25 this doesn't work (yet?)
body['dynamicGroupMetadata'] = {
'queries': [{
'query': sys.argv[i + 1],
@@ -62,7 +65,7 @@ def create():
def delete():
ci = gapi_cloudidentity.build('cloudidentity_beta')
ci = gapi_cloudidentity.build()
group = sys.argv[3]
name = group_email_to_id(ci, group)
print(f'Deleting group {group}')
@@ -75,6 +78,7 @@ def info():
getUsers = True
showJoinDate = True
showUpdateDate = False
showMemberTree = False
i = 4
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
@@ -87,12 +91,15 @@ def info():
elif myarg == 'showupdatedate':
showUpdateDate = True
i += 1
elif myarg == 'membertree':
showMemberTree = True
i += 1
else:
controlflow.invalid_argument_exit(myarg, 'gam info cigroup')
name = group_email_to_id(ci, group)
basic_info = gapi.call(ci.groups(), 'get', name=name)
display.print_json(basic_info)
if getUsers:
if getUsers and not showMemberTree:
if not showJoinDate and not showUpdateDate:
view = 'BASIC'
pageSize = 1000
@@ -106,10 +113,11 @@ def info():
fields='*',
pageSize=pageSize,
view=view)
print('Members:')
print(' Members:')
for member in members:
role = get_single_role(member.get('roles', [])).lower()
email = member.get('memberKey', {}).get('id')
member_type = member.get('type', 'USER').lower()
jc_string = ''
if showJoinDate:
joined = member.get('createTime', 'Unknown')
@@ -117,15 +125,39 @@ def info():
if showUpdateDate:
updated = member.get('updateTime', 'Unknown')
jc_string += f' updated {updated}'
print(
f'{role}: {email}{jc_string}'
# f' {member.get("role", ROLE_MEMBER).lower()}: {member.get("email", member["id"])} ({member["type"].lower()})'
)
print(f' {role}: {email} ({member_type}){jc_string}')
print(f'Total {len(members)} users in group')
elif showMemberTree:
print(' Membership Tree:')
cached_group_members = {}
print_member_tree(ci, name, cached_group_members, 2, True)
def print_member_tree(ci, group_id, cached_group_members, spaces, show_role):
if not group_id in cached_group_members:
cached_group_members[group_id] = gapi.get_all_pages(ci.groups().memberships(),
'list',
'memberships',
parent=group_id,
view='FULL',
fields='*',
pageSize=1000)
for member in cached_group_members[group_id]:
member_id = member.get('name', '')
member_id = member_id.split('/')[-1]
email = member.get('memberKey', {}).get('id')
member_type = member.get('type', 'USER').lower()
if show_role:
role = get_single_role(member.get('roles', [])).lower()
print(f'{" " * spaces}{role}: {email} ({member_type})')
else:
print(f'{" " * spaces}{email} ({member_type})')
if member_type == 'group':
print_member_tree(ci, f'groups/{member_id}', cached_group_members, spaces + 2, False)
def info_member():
ci = gapi_cloudidentity.build('cloudidentity_beta')
ci = gapi_cloudidentity.build()
member = gam.normalizeEmailAddressOrUID(sys.argv[3])
group = gam.normalizeEmailAddressOrUID(sys.argv[4])
group_name = gapi.call(ci.groups(),
@@ -160,6 +192,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 +204,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,16 +259,36 @@ 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')
entityList = gapi.get_all_pages(ci.groups(),
'list',
'groups',
page_message=page_message,
message_attribute=['groupKey', 'id'],
parent=parent,
view='FULL',
pageSize=500)
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,
'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',
page_message=page_message,
message_attribute=['groupKey', 'id'],
parent=parent,
view='FULL',
pageSize=500)
i = 0
count = len(entityList)
for groupEntity in entityList:
@@ -273,7 +330,7 @@ def print_():
ownersCount = 0
for member in groupMembers:
member_email = member['memberKey']['id']
role = get_single_role(member.get('roles'))
role = get_single_role(member.get('roles', []))
if not validRoles or role in validRoles:
if role == ROLE_MEMBER:
if members:
@@ -314,11 +371,62 @@ def print_():
display.write_csv_file(csvRows, titles, 'Groups', todrive)
def _get_groups_list(ci=None, member=None, parent=None):
if not ci:
ci = gapi_cloudidentity.build()
if not parent:
gapi_directory_customer.setTrueCustomerId()
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
gam.printGettingAllItems('Groups', member)
page_message = gapi.got_total_items_first_last_msg('Groups')
if member:
fields = 'nextPageToken,memberships(groupKey(id),relationType)'
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=member,
pageSize=1000,
fields=fields)
except googleapiclient.errors.HttpError:
controlflow.system_error_exit(
2,
'enterprisemember requires Enterprise license')
return [group['groupKey']['id'] for group in groups_to_get if group['relationType'] == 'DIRECT']
else:
groups_to_get = gapi.get_all_pages(
ci.groups(),
'list',
'groups',
message_attribute=['groupKey', 'id'],
page_message=page_message,
parent=parent,
view='BASIC',
pageSize=1000,
fields='nextPageToken,groups(groupKey(id))')
return [group['groupKey']['id'] for group in groups_to_get]
def get_membership_graph(member):
ci = gapi_cloudidentity.build('cloudidentity_beta')
query = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels"
result = gapi.call(ci.groups().memberships(),
'getMembershipGraph',
parent='groups/-',
query=query)
return result.get('response')
def print_members():
ci = gapi_cloudidentity.build('cloudidentity_beta')
todrive = False
gapi_directory_customer.setTrueCustomerId()
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
usemember = None
roles = []
titles = ['group']
csvRows = []
@@ -339,6 +447,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,19 +459,7 @@ def print_members():
controlflow.invalid_argument_exit(sys.argv[i],
'gam print cigroup-members')
if not groups_to_get:
gam.printGettingAllItems('Groups', None)
page_message = gapi.got_total_items_first_last_msg('Groups')
groups_to_get = gapi.get_all_pages(
ci.groups(),
'list',
'groups',
message_attribute=['groupKey', 'id'],
page_message=page_message,
parent=parent,
view='BASIC',
pageSize=1000,
fields='nextPageToken,groups(groupKey(id))')
groups_to_get = [group['groupKey']['id'] for group in groups_to_get]
groups_to_get = _get_groups_list(ci, usemember, parent)
i = 0
count = len(groups_to_get)
for group_email in groups_to_get:
@@ -411,7 +511,7 @@ def update():
def _getRoleAndUsers():
checkSuspended = None
role = None
role = ROLE_MEMBER
expireTime = None
i = 5
if sys.argv[i].lower() in GROUP_ROLES_MAP:
@@ -421,7 +521,10 @@ def update():
checkSuspended = sys.argv[i].lower() == 'suspended'
i += 1
if sys.argv[i].lower() in ['expire', 'expires']:
expireTime = sys.argv[i+1]
if role != ROLE_MEMBER:
controlflow.invalid_argument_exit(
sys.argv[i], f'role {role}')
expireTime = utils.get_time_or_delta_from_now(sys.argv[i+1])
i += 2
if sys.argv[i].lower() in usergroup_types:
users_email = gam.getUsersToModify(entity_type=sys.argv[i].lower(),
@@ -449,8 +552,6 @@ def update():
return
if myarg == 'add':
role, expireTime, users_email = _getRoleAndUsers()
if not role:
role = ROLE_MEMBER
if len(users_email) > 1:
sys.stderr.write(
f'Group: {group}, Will add {len(users_email)} {role}s.\n')
@@ -473,7 +574,7 @@ def update():
}
if role != ROLE_MEMBER:
body['roles'].append({'name': role})
if expireTime:
elif expireTime not in {None, NEVER_TIME}:
for role in body['roles']:
if role['name'] == ROLE_MEMBER:
role['expiryDetail'] = {'expireTime': expireTime}
@@ -544,7 +645,7 @@ def update():
for user in to_add:
item = ['gam', 'update', 'cigroup', f'id:{parent}', 'add',
role,]
if expireTime:
if role == ROLE_MEMBER and expireTime not in {None, NEVER_TIME}:
item.extend(['expires', expireTime])
item.append(user)
items.append(item)
@@ -580,8 +681,6 @@ def update():
)
elif myarg == 'update':
role, expireTime, users_email = _getRoleAndUsers()
if not role:
role = ROLE_MEMBER
if len(users_email) > 1:
sys.stderr.write(
f'Group: {group}, Will update {len(users_email)} {role}s.\n'
@@ -596,36 +695,48 @@ def update():
items.append(item)
elif len(users_email) > 0:
name = membership_email_to_id(ci, parent, users_email[0])
preUpdateRoles = []
addRoles = []
removeRoles = []
current_roles = gapi.call(ci.groups().memberships(),
'get',
name=name,
fields='roles').get('roles', [])
current_roles = [role['name'] for role in current_roles]
postUpdateRoles = []
member_roles = gapi.call(ci.groups().memberships(),
'get',
name=name,
fields='roles').get('roles', [{'name': ROLE_MEMBER}])
current_roles = [crole['name'] for crole in member_roles]
# When upgrading role, strip any expiryDetail from member before role changes
if role != ROLE_MEMBER:
for crole in member_roles:
if 'expiryDetail' in crole:
preUpdateRoles.append(
{'fieldMask': 'expiryDetail.expireTime',
'membershipRole': {'name': ROLE_MEMBER,
'expiryDetail': {'expireTime': None}}})
break
# When downgrading role or simply updating member expireTime, update expiryDetail after role changes
elif expireTime:
postUpdateRoles.append(
{'fieldMask': 'expiryDetail.expireTime',
'membershipRole': {'name': role,
'expiryDetail': {'expireTime': expireTime if expireTime != NEVER_TIME else None}}})
for crole in current_roles:
if crole not in {ROLE_MEMBER, role}:
removeRoles.append(crole)
if role not in current_roles:
new_role = {'name': role}
if role == ROLE_MEMBER and expireTime:
if role == ROLE_MEMBER and expireTime not in {None, NEVER_TIME}:
new_role['expiryDetail'] = {'expireTime': expireTime}
expireTime = None
postUpdateRoles = []
addRoles.append(new_role)
bodys = []
if preUpdateRoles:
bodys.append({'updateRolesParams': preUpdateRoles})
if addRoles:
bodys.append({'addRoles': addRoles})
if removeRoles:
bodys.append({'removeRoles': removeRoles})
if expireTime:
bodys.append({
'name': ROLE_MEMBER,
# Note this doesn't actually work for some reason. Only known method to change
# expire time right now is to remove/re-add member.
'expiryDetail': {
'expireTime': expireTime
}
})
if postUpdateRoles:
bodys.append({'updateRolesParams': postUpdateRoles})
for body in bodys:
try:
gapi.call(ci.groups().memberships(),
@@ -711,6 +822,14 @@ def update():
'cloudidentity.googleapis.com/groups.discussion_forum': ''
}
i += 1
elif myarg in ['dynamic']:
body['dynamicGroupMetadata'] = {
'queries': [{
'query': sys.argv[i + 1],
'resourceType': 'USER'
}]
}
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam update cigroup')

View File

@@ -0,0 +1,207 @@
"""Methods related to Cloud Identity User Invitation API"""
import sys
from urllib.parse import quote_plus
import googleapiclient
import gam
from gam.var import GC_CUSTOMER_ID, GC_Values, MY_CUSTOMER, SORTORDER_CHOICES_MAP
from gam import controlflow
from gam import display
from gam import gapi
from gam.gapi import errors as gapi_errors
from gam.gapi import cloudidentity as gapi_cloudidentity
def _get_customerid():
''' returns customer in "customers/(C){customer_id}' format needed for this API'''
customer = GC_Values[GC_CUSTOMER_ID]
if customer != MY_CUSTOMER and customer[0] != 'C':
customer = 'C' + customer
return f'customers/{customer}'
def _reduce_name(name):
''' converts long name into email address'''
return name.split('/')[-1]
def is_invitable_user(email):
'''return email isInvitableUser'''
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
customer = _get_customerid()
encoded_email = quote_plus(email)
name = f'{customer}/userinvitations/{encoded_email}'
return gapi.call(svc.customers().userinvitations(), 'isInvitableUser',
name=name)['isInvitableUser']
def _generic_action(action):
'''generic function to call actionable APIs'''
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
customer = _get_customerid()
email = sys.argv[3].lower()
encoded_email = quote_plus(email)
name = f'{customer}/userinvitations/{encoded_email}'
action_map = {
'cancel': 'Cancelling',
'send': 'Sending'
}
print_action = action_map[action]
print(f'{print_action} user invitation...')
result = gapi.call(svc.customers().userinvitations(), action,
name=name)
name = result.get('response', {}).get('name')
if name:
result['response']['name'] = _reduce_name(name)
display.print_json(result)
def _generic_get(get_type):
'''generic function to call read data APIs'''
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
customer = _get_customerid()
email = sys.argv[3].lower()
encoded_email = quote_plus(email)
name = f'{customer}/userinvitations/{encoded_email}'
result = gapi.call(svc.customers().userinvitations(), get_type,
name=name)
if 'name' in result:
result['name'] = _reduce_name(result['name'])
display.print_json(result)
# /batch is broken for Cloud Identity. Once fixed move this to using batch.
# Current serial implementation will be SLOW...
def bulk_is_invitable(emails):
'''gam <users> check isinvitable'''
def _invitation_result(request_id, response, _):
if response.get('isInvitableUser'):
rows.append({'invitableUsers': request_id})
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
customer = _get_customerid()
todrive = False
#batch_size = 1000
#ebatch = svc.new_batch_http_request(callback=_invitation_result)
rows = []
throw_reasons = [gapi_errors.ErrorReason.FOUR_O_THREE]
for email in emails:
encoded_email = quote_plus(email)
name = f'{customer}/userinvitations/{encoded_email}'
endpoint = svc.customers().userinvitations()
#if len(ebatch._order) == batch_size:
# ebatch.execute()
# ebatch = svc.new_batch_http_request(callback=_invitation_result)
#req = endpoint.isInvitableUser(name=name)
#ebatch.add(req, request_id=email)
try:
result = gapi.call(endpoint,
'isInvitableUser',
throw_reasons=throw_reasons,
name=name)
except googleapiclient.errors.HttpError:
continue
if result.get('isInvitableUser'):
rows.append({'invitableUsers': email})
#ebatch.execute()
titles = ['invitableUsers']
display.write_csv_file(rows, titles, 'Invitable Users', todrive)
def cancel():
'''gam cancel userinvitation <email>'''
_generic_action('cancel')
def get():
'''gam info userinvitation <email>'''
_generic_get('get')
def check():
'''gam check userinvitation <email>'''
_generic_get('isInvitableUser')
def send():
'''gam send userinvitation <email>'''
_generic_action('send')
USERINVITATION_ORDERBY_CHOICES_MAP = {
'email': 'email',
'updatetime': 'update_time',
}
USERINVITATION_STATE_CHOICES_MAP = {
'accepted': 'ACCEPTED',
'declined': 'DECLINED',
'invited': 'INVITED',
'notyetsent': 'NOT_YET_SENT',
}
def print_():
'''gam print userinvitations'''
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
customer = _get_customerid()
todrive = False
titles = ['name', 'state', 'updateTime']
rows = []
filter_ = None
orderByList = []
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'state':
state = sys.argv[i + 1].lower().replace('_', '')
if state in USERINVITATION_STATE_CHOICES_MAP:
filter_ = f"state=='{USERINVITATION_STATE_CHOICES_MAP[state]}'"
else:
controlflow.expected_argument_exit('state',
', '.join(USERINVITATION_STATE_CHOICES_MAP),
state)
i += 2
elif myarg == 'orderby':
fieldName = sys.argv[i + 1].lower()
i += 2
if fieldName in USERINVITATION_ORDERBY_CHOICES_MAP:
fieldName = USERINVITATION_ORDERBY_CHOICES_MAP[fieldName]
orderBy = ''
if i < len(sys.argv):
orderBy = sys.argv[i].lower()
if orderBy in SORTORDER_CHOICES_MAP:
orderBy = SORTORDER_CHOICES_MAP[orderBy]
i += 1
if orderBy != 'DESCENDING':
orderByList.append(fieldName)
else:
orderByList.append(f'{fieldName} desc')
else:
controlflow.expected_argument_exit(
'orderby', ', '.join(sorted(USERINVITATION_ORDERBY_CHOICES_MAP)),
fieldName)
elif myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam print userinvitations')
if orderByList:
orderBy = ' '.join(orderByList)
else:
orderBy = None
gam.printGettingAllItems('User Invitations', filter_)
page_message = gapi.got_total_items_msg('User Invitations', '...\n')
invitations = gapi.get_all_pages(svc.customers().userinvitations(),
'list',
'userInvitations',
page_message=page_message,
parent=customer,
filter=filter_,
orderBy=orderBy)
for invitation in invitations:
invitation['name'] = _reduce_name(invitation['name'])
row = {}
for key, val in invitation.items():
if key not in titles:
titles.append(key)
row[key] = val
rows.append(row)
display.write_csv_file(rows, titles, 'User Invitations', todrive)

View File

@@ -0,0 +1,96 @@
"""Contact Delegation API calls"""
import sys
import gam
from gam.gapi.directory import users as gapi_directory_users
from gam import controlflow
from gam import display
from gam import gapi
def build():
return gam.buildGAPIObject('contactdelegation')
def create(users):
condel = build()
delegate = gam.normalizeEmailAddressOrUID(sys.argv[5])
delegate = gapi_directory_users.get_primary(delegate)
if not delegate:
controlflow.system_error_exit(5,
f'{sys.argv[5]} is not the primary address of a user.')
body = {'email': delegate}
i = 0
count = len(users)
for user in users:
i += 1
print(
f'Granting {delegate} contact delegate access to {user}{gam.currentCount(i, count)}'
)
gapi.call(condel.delegates(),
'create',
soft_errors=True,
user=user,
body=body)
def delete(users):
condel = build()
delegate = gam.normalizeEmailAddressOrUID(sys.argv[5])
delegate = gapi_directory_users.get_primary(delegate)
if not delegate:
controlflow.system_error_exit(5,
f'{sys.argv[5]} is not the primary address of a user.')
i = 0
count = len(users)
for user in users:
i += 1
print(
f'Deleting {delegate} contact delegate access to {user}{gam.currentCount(i, count)}'
)
gapi.call(condel.delegates(),
'delete',
soft_errors=True,
user=user,
delegate=delegate)
def print_(users, csvFormat):
condel = build()
if csvFormat:
todrive = False
csv_rows = []
titles = ['User', 'delegateAddress']
else:
csvStyle = False
i = 5
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if not csvFormat and myarg == 'csv':
csvStyle = True
i += 1
elif csvFormat and myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam print contactdelegation')
page_message = gapi.got_total_items_msg('Contact Delegates', '...\n')
for user in users:
delegates = gapi.get_all_pages(condel.delegates(), 'list',
'delegates',
page_message=page_message,
user=user)
for delegate in delegates:
if csvFormat:
csv_rows.append({'User': user, 'delegateAddress': delegate['email']})
else:
if csvStyle:
print(f'{user},{delegate["email"]}')
else:
print(
f'Delegator: {user}\n Delegate Email: {delegate["email"]}\n'
)
if csvFormat:
display.write_csv_file(csv_rows, titles, 'Contact Delegates', todrive)

View File

@@ -1,4 +1,7 @@
import datetime
import json
import os
import sys
import time
import googleapiclient
@@ -15,11 +18,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']
@@ -40,7 +54,7 @@ def issue_command():
body['commandType'] = command_map[command]
i += 2
if command == 'setvolume':
body['payload'] = {'volume': int(sys.argv[i])}
body['payload'] = json.dumps({'volume': sys.argv[i]})
i += 1
elif myarg == 'timestocheckstatus':
times_to_check_status = int(sys.argv[i+1])
@@ -50,6 +64,8 @@ def issue_command():
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam issuecommand cros')
if 'commandType' not in body:
controlflow.missing_argument_exit('command <CrOSCommand>', 'gam issuecommand cros')
if body['commandType'] == 'WIPE_USERS' and not doit:
controlflow.system_error_exit(2, 'wipe_users command requires admin ' \
'acknowledge user data will be destroyed with the ' \
@@ -67,36 +83,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()
@@ -388,9 +396,10 @@ def doGetCrosInfo():
temp_label = tempInfo['label'].strip()
temperature = tempInfo['temperature']
print(f' {temp_label}: {temperature}')
pct_info = cpuStatusReport['cpuUtilizationPercentageInfo']
util = ','.join([str(x) for x in pct_info])
print(f' cpuUtilizationPercentageInfo: {util}')
if 'cpuUtilizationPercentageInfo' in cpuStatusReport:
pct_info = cpuStatusReport['cpuUtilizationPercentageInfo']
util = ','.join([str(x) for x in pct_info])
print(f' cpuUtilizationPercentageInfo: {util}')
diskVolumeReports = cros.get('diskVolumeReports', [])
lenDVR = len(diskVolumeReports)
if lenDVR:
@@ -825,16 +834,16 @@ def doPrintCrosDevices():
if i < lenCSR:
nrow['cpuStatusReports.reportTime'] = \
cpuStatusReports[i]['reportTime']
tempInfos = cpuStatusReports[i].get('cpuTemperatureInfo',
[])
tempInfos = cpuStatusReports[i].get('cpuTemperatureInfo', [])
for tempInfo in tempInfos:
label = tempInfo['label'].strip()
base = 'cpuStatusReports.cpuTemperatureInfo.'
nrow[f'{base}{label}'] = tempInfo['temperature']
cpu_field = 'cpuUtilizationPercentageInfo'
cpu_reports = cpuStatusReports[i][cpu_field]
cpu_pcts = [str(x) for x in cpu_reports]
nrow[f'cpuStatusReports.{cpu_field}'] = ','.join(cpu_pcts)
if cpu_field in cpuStatusReports[i]:
cpu_reports = cpuStatusReports[i][cpu_field]
cpu_pcts = [str(x) for x in cpu_reports]
nrow[f'cpuStatusReports.{cpu_field}'] = ','.join(cpu_pcts)
if i < lenDVR:
volumeInfo = diskVolumeReports[i]['volumeInfo']
j = 0

View File

@@ -8,18 +8,25 @@ from gam.gapi import directory as gapi_directory
from gam.gapi import reports as gapi_reports
def _get_customerid():
customer = GC_Values[GC_CUSTOMER_ID]
if customer != MY_CUSTOMER and customer[0] != 'C':
customer = 'C' + customer
return customer
def doGetCustomerInfo():
cd = gapi_directory.build()
customer_id = _get_customerid()
customer_info = gapi.call(cd.customers(),
'get',
customerKey=GC_Values[GC_CUSTOMER_ID])
customerKey=customer_id)
print(f'Customer ID: {customer_info["id"]}')
print(f'Primary Domain: {customer_info["customerDomain"]}')
try:
result = gapi.call(
cd.domains(),
'get',
customer=customer_info['id'],
customer=customer_id,
domainName=customer_info['customerDomain'],
fields='verified',
throw_reasons=[gapi.errors.ErrorReason.DOMAIN_NOT_FOUND])
@@ -35,7 +42,7 @@ def doGetCustomerInfo():
domains = gapi.get_items(cd.domains(),
'list',
'domains',
customer=GC_Values[GC_CUSTOMER_ID],
customer=customer_id,
fields='domains(creationTime)')
for domain in domains:
creation_timestamp = int(domain['creationTime']) / 1000
@@ -57,9 +64,9 @@ def doGetCustomerInfo():
'accounts:num_users': 'Total Users',
'accounts:gsuite_basic_total_licenses': 'G Suite Basic Licenses',
'accounts:gsuite_basic_used_licenses': 'G Suite Basic Users',
'accounts:gsuite_enterprise_total_licenses': 'G Suite Enterprise ' \
'accounts:gsuite_enterprise_total_licenses': 'Workspace Enterprise Plus ' \
'Licenses',
'accounts:gsuite_enterprise_used_licenses': 'G Suite Enterprise ' \
'accounts:gsuite_enterprise_used_licenses': 'Workspace Enterprise Plus ' \
'Users',
'accounts:gsuite_unlimited_total_licenses': 'G Suite Business ' \
'Licenses',
@@ -67,9 +74,9 @@ def doGetCustomerInfo():
}
parameters = ','.join(list(user_counts_map))
tryDate = datetime.date.today().strftime(YYYYMMDD_FORMAT)
customerId = GC_Values[GC_CUSTOMER_ID]
if customerId == MY_CUSTOMER:
customerId = None
reports_customer_id = customer_id
if reports_customer_id == MY_CUSTOMER:
reports_customer_id = None
rep = gapi_reports.build()
usage = None
throw_reasons = [
@@ -80,7 +87,7 @@ def doGetCustomerInfo():
result = gapi.call(rep.customerUsageReports(),
'get',
throw_reasons=throw_reasons,
customerId=customerId,
customerId=reports_customer_id,
date=tryDate,
parameters=parameters)
except gapi.errors.GapiInvalidError as e:
@@ -111,6 +118,7 @@ def doGetCustomerInfo():
def doUpdateCustomer():
cd = gapi_directory.build()
body = {}
customer_id = _get_customerid()
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
@@ -136,14 +144,19 @@ def doUpdateCustomer():
'update customer"')
gapi.call(cd.customers(),
'patch',
customerKey=GC_Values[GC_CUSTOMER_ID],
customerKey=customer_id,
body=body)
print('Updated customer')
def setTrueCustomerId():
if GC_Values[GC_CUSTOMER_ID] == MY_CUSTOMER:
cd = gapi_directory.build()
GC_Values[GC_CUSTOMER_ID] = gapi.call(cd.customers(), 'get',
customerKey=GC_Values[GC_CUSTOMER_ID],
fields='id').get('id', GC_Values[GC_CUSTOMER_ID])
def setTrueCustomerId(cd=None):
customer_id = GC_Values[GC_CUSTOMER_ID]
if customer_id == MY_CUSTOMER:
if not cd:
cd = gapi_directory.build()
result = gapi.call(cd.customers(),
'get',
customerKey=customer_id,
fields='id')
GC_Values[GC_CUSTOMER_ID] = result.get('id',
customer_id)

View File

@@ -7,18 +7,22 @@ from gam import display
from gam import gapi
from gam.gapi import directory as gapi_directory
from gam.gapi import errors as gapi_errors
from gam.gapi.directory import customer as gapi_directory_customer
from gam import utils
from gam.gapi.cloudidentity import userinvitations as gapi_cloudidentity_userinvitations
def GroupIsAbuseOrPostmaster(emailAddr):
return emailAddr.startswith('abuse@') or emailAddr.startswith('postmaster@')
def mapGroupEmailForSettings(emailAddr):
return emailAddr.replace('/', '%2F')
def create():
cd = gapi_directory.build()
body = {'email': gam.normalizeEmailAddressOrUID(sys.argv[3], noUid=True)}
gs_get_before_update = got_name = False
verifyNotInvitable = False
i = 4
gs_body = {}
gs = None
@@ -47,6 +51,9 @@ def create():
elif myarg == 'getbeforeupdate':
gs_get_before_update = True
i += 1
elif myarg == 'verifynotinvitable':
verifyNotInvitable = True
i += 1
else:
if not gs:
gs = gam.buildGAPIObject('groupssettings')
@@ -56,6 +63,10 @@ def create():
i += 2
if not got_name:
body['name'] = body['email']
if (verifyNotInvitable and
gapi_cloudidentity_userinvitations.get_is_invitable_user(body['email'])):
sys.stderr.write(f'Group not created, {body["email"]} is an unmanaged account\n')
sys.exit(51)
print(f'Creating group {body["email"]}')
gapi.call(cd.groups(), 'insert', body=body, fields='email')
if gs and not GroupIsAbuseOrPostmaster(body['email']):
@@ -67,7 +78,7 @@ def create():
gapi_errors.ErrorReason.SERVICE_LIMIT,
gapi_errors.ErrorReason.NOT_FOUND
],
groupUniqueId=body['email'],
groupUniqueId=mapGroupEmailForSettings(body['email']),
fields='*')
if current_settings is not None:
gs_body = dict(
@@ -75,7 +86,7 @@ def create():
if gs_body:
gapi.call(gs.groups(),
'update',
groupUniqueId=body['email'],
groupUniqueId=mapGroupEmailForSettings(body['email']),
retry_reasons=[
gapi_errors.ErrorReason.SERVICE_LIMIT,
gapi_errors.ErrorReason.NOT_FOUND
@@ -170,7 +181,7 @@ def info(group_name=None):
'get',
throw_reasons=[gapi_errors.ErrorReason.AUTH_ERROR],
retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
groupUniqueId=basic_info['email']
groupUniqueId=mapGroupEmailForSettings(basic_info['email'])
) # Use email address retrieved from cd since GS API doesn't support uid
if settings is None:
settings = {}
@@ -589,7 +600,7 @@ def print_():
gapi_errors.ErrorReason.SERVICE_LIMIT,
gapi_errors.ErrorReason.INVALID
],
groupUniqueId=groupEmail,
groupUniqueId=mapGroupEmailForSettings(groupEmail),
fields=gsfields)
if settings:
for key in settings:
@@ -1134,6 +1145,7 @@ def update():
else:
i = 4
use_cd_api = False
verifyNotInvitable = False
gs = None
gs_body = {}
cd_body = {}
@@ -1151,6 +1163,9 @@ def update():
elif myarg == 'getbeforeupdate':
gs_get_before_update = True
i += 1
elif myarg == 'verifynotinvitable':
verifyNotInvitable = True
i += 1
else:
if not gs:
gs = gam.buildGAPIObject('groupssettings')
@@ -1162,6 +1177,10 @@ def update():
if use_cd_api or (
group.find('@') == -1
): # group settings API won't take uid so we make sure cd API is used so that we can grab real email.
if (verifyNotInvitable and 'email' in cd_body and
gapi_cloudidentity_userinvitations.get_is_invitable_user(cd_body['email'])):
sys.stderr.write(f'Group {group} not updated, new email {cd_body["email"]} is an unmanaged account\n')
sys.exit(51)
group = gapi.call(cd.groups(),
'update',
groupKey=group,
@@ -1174,7 +1193,7 @@ def update():
gs.groups(),
'get',
retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
groupUniqueId=group,
groupUniqueId=mapGroupEmailForSettings(group),
fields='*')
if current_settings is not None:
gs_body = dict(
@@ -1185,7 +1204,7 @@ def update():
gs.groups(),
'update',
retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
groupUniqueId=group,
groupUniqueId=mapGroupEmailForSettings(group),
body=gs_body)
print(f'updated group {group}')

View File

@@ -27,13 +27,13 @@ def info():
'get',
customerId=GC_Values[GC_CUSTOMER_ID],
resourceId=resourceId)
if 'deviceId' in info:
if 'deviceId' in device_info:
device_info['deviceId'] = device_info['deviceId'].encode('unicode-escape').decode(
UTF8)
attrib = 'securityPatchLevel'
if attrib in info and int(device_info[attrib]):
if attrib in device_info and int(device_info[attrib]):
device_info[attrib] = utils.formatTimestampYMDHMS(device_info[attrib])
display.print_json(info)
display.print_json(device_info)

View File

@@ -402,20 +402,18 @@ def getOrgUnitId(orgUnit, cd=None):
return (orgUnit, result['orgUnitId'])
def buildOrgUnitIdToNameMap():
cd = gapi_directory.build()
result = gapi.call(cd.orgunits(),
'list',
customerId=GC_Values[GC_CUSTOMER_ID],
fields='organizationUnits(orgUnitPath,orgUnitId)',
type='all')
GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME] = {}
for orgUnit in result['organizationUnits']:
GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME][
orgUnit['orgUnitId']] = orgUnit['orgUnitPath']
def orgunit_from_orgunitid(orgunitid):
if not GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME]:
buildOrgUnitIdToNameMap()
return GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME].get(orgunitid, orgunitid)
def orgunit_from_orgunitid(orgunitid, cd=None):
if cd is None:
cd = gapi_directory.build()
orgunitpath = GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME].get(orgunitid)
if not orgunitpath:
try:
orgunitpath = gapi.call(cd.orgunits(),
'get',
customerId=GC_Values[GC_CUSTOMER_ID],
orgUnitPath=f'id:{orgunitid}' if not orgunitid.startswith('id:') else orgunitid,
fields='orgUnitPath')['orgUnitPath']
except:
orgunitpath = orgunitid
GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME][orgunitid] = orgunitpath
return orgunitpath

View File

@@ -0,0 +1,187 @@
'''Commands to manage directory printers.'''
# pylint: disable=unused-wildcard-import wildcard-import
import sys
import gam
from gam import controlflow
from gam import display
from gam import gapi
from gam.var import *
from gam.gapi import directory as gapi_directory
from gam.gapi.directory import orgunits as gapi_directory_orgunits
def _get_customerid():
''' returns customer in "customers/C{customer}" format needed for this API'''
customer = GC_Values[GC_CUSTOMER_ID]
if customer != MY_CUSTOMER and customer[0] != 'C':
customer = 'C' + customer
return f'customers/{customer}'
def _get_printer_attributes(i, cdapi=None):
'''get printer attributes for create/update commands'''
body = {}
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'description':
body['description'] = sys.argv[i+1]
i += 2
elif myarg == 'displayname':
body['displayName'] = sys.argv[i+1]
i += 2
elif myarg == 'makeandmodel':
body['makeAndModel'] = sys.argv[i+1]
i += 2
elif myarg in ['ou', 'org', 'orgunit', 'orgunitid']:
_, body['orgUnitId'] = gapi_directory_orgunits.getOrgUnitId(sys.argv[i+1], cdapi)
body['orgUnitId'] = body['orgUnitId'][3:]
i += 2
elif myarg == 'uri':
body['uri'] = sys.argv[i+1]
i += 2
elif myarg in {'driverless', 'usedriverlessconfig'}:
body['useDriverlessConfig'] = True
i += 1
return body
def create():
'''gam create printer'''
cdapi = gapi_directory.build()
parent = _get_customerid()
body = _get_printer_attributes(3, cdapi)
result = gapi.call(cdapi.customers().chrome().printers(),
'create',
parent=parent,
body=body)
display.print_json(result)
def delete():
'''gam delete printer <PrinterIDList>|(file <FileName>)|(csvfile <FileName>:<FieldName>)'''
cdapi = gapi_directory.build()
customer_id = _get_customerid()
printer_id = sys.argv[3]
if printer_id.lower() not in {'file', 'csvfile'}:
printer_ids = printer_id.replace(',', ' ').split()
else:
printer_ids = gam.getUsersToModify(f'cros{printer_id.lower()}', sys.argv[4])
# max 50 per API call
batch_size = 50
for chunk in range(0, len(printer_ids), batch_size):
body = {
'printerIds': printer_ids[chunk:chunk + batch_size]
}
result = gapi.call(cdapi.customers().chrome().printers(),
'batchDeletePrinters',
parent=customer_id,
body=body)
for printer_id in result.get('printerIds', []):
print(f'Deleted printer {printer_id}')
for printer_id in result.get('failedPrinters', []):
print(f'ERROR: failed to delete {printer_id.get("printerIds")}')
def info():
'''gam info printer'''
cdapi = gapi_directory.build()
customer = _get_customerid()
printer_id = sys.argv[3]
name = f'{customer}/chrome/printers/{printer_id}'
printer = gapi.call(cdapi.customers().chrome().printers(),
'get',
name=name)
if 'orgUnitId' in printer:
printer['orgUnitPath'] = gapi_directory_orgunits.orgunit_from_orgunitid(
printer['orgUnitId'], cdapi)
display.print_json(printer)
def print_():
'''gam print printers'''
cdapi = gapi_directory.build()
parent = _get_customerid()
filter_ = None
todrive = False
titles = []
rows = []
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'filter':
filter_ = sys.argv[i+1]
i += 2
elif myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam print printermodels')
printers = gapi.get_all_pages(cdapi.customers().chrome().printers(),
'list',
items='printers',
parent=parent,
filter=filter_)
for printer in printers:
if 'orgUnitId' in printer:
printer['orgUnitPath'] = gapi_directory_orgunits.orgunit_from_orgunitid(
printer['orgUnitId'], cdapi)
row = {}
for key, val in printer.items():
if key not in titles:
titles.append(key)
row[key] = val
rows.append(row)
display.write_csv_file(rows, titles, 'Printers', todrive)
def print_models():
'''gam print printermodels'''
cdapi = gapi_directory.build()
parent = _get_customerid()
filter_ = None
todrive = False
titles = []
rows = []
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'filter':
filter_ = sys.argv[i+1]
i += 2
elif myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam print printermodels')
models = gapi.get_all_pages(cdapi.customers().chrome().printers(),
'listPrinterModels',
items='printerModels',
parent=parent,
pageSize=10000,
filter=filter_)
for model in models:
row = {}
for key, val in model.items():
if key not in titles:
titles.append(key)
row[key] = val
rows.append(row)
display.write_csv_file(rows, titles, 'Printer Models', todrive)
def update():
'''gam update printer'''
cdapi = gapi_directory.build()
customer = _get_customerid()
printer_id = sys.argv[3]
name = f'{customer}/chrome/printers/{printer_id}'
body = _get_printer_attributes(4, cdapi)
update_mask = ','.join(body)
# note clearMask seems unnecessary. Updating field to '' clears it.
result = gapi.call(cdapi.customers().chrome().printers(),
'patch',
name=name,
updateMask=update_mask,
body=body)
display.print_json(result)

View File

@@ -1,7 +1,22 @@
from time import sleep
import gam
from gam import gapi
from gam.gapi import directory as gapi_directory
def get_primary(email):
'''returns primary email of user or empty if email is not a user primary or
alias address.'''
cd = gapi_directory.build()
result = gapi.call(cd.users(), 'get', userKey=email,
projection='basic', fields='primaryEmail',
soft_errors=True)
if not result:
return ''
return result.get('primaryEmail', '').lower()
def signout(users):
cd = gapi_directory.build()
i = 0
@@ -28,3 +43,22 @@ def turn_off_2sv(users):
'turnOff',
soft_errors=True,
userKey=user)
def wait_for_mailbox(users):
'''Wait until users mailbox is provisioned.'''
cd = gapi_directory.build()
i = 0
count = len(users)
for user in users:
i += 1
user = gam.normalizeEmailAddressOrUID(user)
while True:
result = gapi.call(cd.users(),
'get',
'fields=isMailboxSetup',
userKey=user)
mailbox_is_setup = result.get('isMailboxSetup')
print(f'{user} mailboxIsSetup: {mailbox_is_setup}')
if mailbox_is_setup:
break
sleep(3)

View File

@@ -116,8 +116,10 @@ class ErrorReason(Enum):
DUPLICATE = 'duplicate'
FAILED_PRECONDITION = 'failedPrecondition'
FORBIDDEN = 'forbidden'
FIVE_O_THREE = '503'
FOUR_O_NINE = '409'
FOUR_O_O = '400'
FOUR_O_FOUR = '404'
FOUR_O_THREE = '403'
FOUR_TWO_NINE = '429'
GATEWAY_TIMEOUT = 'gatewayTimeout'
@@ -153,6 +155,7 @@ DEFAULT_RETRY_REASONS = [
ErrorReason.GATEWAY_TIMEOUT,
ErrorReason.INTERNAL_ERROR,
ErrorReason.FOUR_TWO_NINE,
ErrorReason.FIVE_O_THREE,
]
GMAIL_THROW_REASONS = [ErrorReason.SERVICE_NOT_AVAILABLE]
GROUP_GET_THROW_REASONS = [

View File

@@ -7,6 +7,7 @@ from unittest.mock import patch
import googleapiclient.errors
from gam.gapi import errors
import httplib2
def create_simple_http_error(status, reason, message):
@@ -15,10 +16,10 @@ def create_simple_http_error(status, reason, message):
def create_http_error(status, content):
response = {
response = httplib2.Response({
'status': status,
'content-type': 'application/json',
}
})
content_as_bytes = json.dumps(content).encode('UTF-8')
return googleapiclient.errors.HttpError(response, content_as_bytes)
@@ -73,6 +74,7 @@ class ErrorsTest(unittest.TestCase):
def test_get_gapi_error_extracts_user_not_found(self):
err = create_simple_http_error(404, 'notFound',
'Resource Not Found: userKey.')
print(err)
http_status, reason, message = errors.get_gapi_error_detail(err)
self.assertEqual(http_status, 404)
self.assertEqual(reason, errors.ErrorReason.USER_NOT_FOUND.value)
@@ -158,7 +160,7 @@ class ErrorsTest(unittest.TestCase):
def test_get_gapi_error_extracts_single_error_with_message(self):
status_code = 999
response = {'status': status_code}
response = httplib2.Response({'status': status_code})
# This error does not have an "errors" key describing each error.
content = {'error': {'code': status_code, 'message': 'unknown error'}}
content_as_bytes = json.dumps(content).encode('UTF-8')
@@ -172,7 +174,7 @@ class ErrorsTest(unittest.TestCase):
def test_get_gapi_error_exits_code_4_on_malformed_error_with_unknown_description(
self):
status_code = 999
response = {'status': status_code}
response = httplib2.Response({'status': status_code})
# This error only has an error_description_field and an unknown description.
content = {'error_description': 'something errored'}
content_as_bytes = json.dumps(content).encode('UTF-8')
@@ -184,7 +186,7 @@ class ErrorsTest(unittest.TestCase):
def test_get_gapi_error_exits_on_invalid_error_description(self):
status_code = 400
response = {'status': status_code}
response = httplib2.Response({'status': status_code})
content = {'error_description': 'Invalid Value'}
content_as_bytes = json.dumps(content).encode('UTF-8')
err = googleapiclient.errors.HttpError(response, content_as_bytes)
@@ -196,7 +198,7 @@ class ErrorsTest(unittest.TestCase):
def test_get_gapi_error_exits_code_4_on_unexpected_error_contents(self):
status_code = 900
response = {'status': status_code}
response = httplib2.Response({'status': status_code})
content = {'notErrorContentThatIsExpected': 'foo'}
content_as_bytes = json.dumps(content).encode('UTF-8')
err = googleapiclient.errors.HttpError(response, content_as_bytes)

View File

@@ -7,8 +7,17 @@ from gam import controlflow
from gam import display
from gam import gapi
from gam.gapi import errors as gapi_errors
from gam.gapi.directory import customer as gapi_directory_customer
def _get_customerid():
''' returns customerId with format C{customer_id}'''
gapi_directory_customer.setTrueCustomerId()
customer_id = GC_Values[GC_CUSTOMER_ID]
if customer_id[0] != 'C':
customer_id = 'C' + customer_id
return customer_id
def build():
return gam.buildGAPIObject('licensing')
@@ -127,6 +136,7 @@ def print_(returnFields=None,
countsOnly=False,
returnCounts=False):
lic = build()
customer_id = _get_customerid()
products = []
licenses = []
licenseCounts = []
@@ -193,7 +203,7 @@ def print_(returnFields=None,
gapi_errors.ErrorReason.FORBIDDEN
],
page_message=page_message,
customerId=GC_Values[GC_DOMAIN],
customerId=customer_id,
productId=product,
skuId=sku,
fields=fields)
@@ -223,7 +233,7 @@ def print_(returnFields=None,
gapi_errors.ErrorReason.FORBIDDEN
],
page_message=page_message,
customerId=GC_Values[GC_DOMAIN],
customerId=customer_id,
productId=productId,
fields=fields)
if countsOnly:

View File

@@ -1,6 +1,7 @@
import datetime
import json
import sys
from time import sleep
import googleapiclient.http
@@ -11,6 +12,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
@@ -84,21 +86,120 @@ VAULT_SEARCH_METHODS_MAP = {
VAULT_SEARCH_METHODS_LIST = [
'accounts', 'orgunit', 'shareddrives', 'rooms', 'everyone'
]
QUERY_ARGS = ['corpus', 'scope', 'terms', 'start', 'starttime',
'end', 'endtime', 'timezone', 'excludedrafts',
'driveversiondate', 'includeshareddrives', 'includeteamdrives',
'includerooms'] + list(VAULT_SEARCH_METHODS_MAP.keys())
def _build_query(query, myarg, i, query_discovery):
if not query:
query = {'dataScope': 'ALL_DATA'}
if myarg == 'corpus':
query['corpus'] = sys.argv[i + 1].upper()
allowed_corpuses = gapi.get_enum_values_minus_unspecified(
query_discovery['properties']['corpus']['enum'])
if query['corpus'] not in allowed_corpuses:
controlflow.expected_argument_exit('corpus',
', '.join(allowed_corpuses),
sys.argv[i + 1])
i += 2
elif myarg in VAULT_SEARCH_METHODS_MAP:
if query.get('searchMethod'):
message = f'Multiple search methods ' \
f'({", ".join(VAULT_SEARCH_METHODS_LIST)})' \
f'specified, only one is allowed'
controlflow.system_error_exit(3, message)
searchMethod = VAULT_SEARCH_METHODS_MAP[myarg]
query['searchMethod'] = searchMethod
if searchMethod == 'ACCOUNT':
query['accountInfo'] = {
'emails': sys.argv[i + 1].split(',')
}
i += 2
elif searchMethod == 'ORG_UNIT':
query['orgUnitInfo'] = {
'orgUnitId': gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])[1]
}
i += 2
elif searchMethod == 'SHARED_DRIVE':
query['sharedDriveInfo'] = {
'sharedDriveIds': sys.argv[i + 1].split(',')
}
i += 2
elif searchMethod == 'ROOM':
query['hangoutsChatInfo'] = {
'roomId': sys.argv[i + 1].split(',')
}
i += 2
else:
i += 1
elif myarg == 'scope':
query['dataScope'] = sys.argv[i + 1].upper()
allowed_scopes = gapi.get_enum_values_minus_unspecified(
query_discovery['properties']['dataScope']['enum'])
if query['dataScope'] not in allowed_scopes:
controlflow.expected_argument_exit('scope',
', '.join(allowed_scopes),
sys.argv[i + 1])
i += 2
elif myarg in ['terms']:
query['terms'] = sys.argv[i + 1]
i += 2
elif myarg in ['start', 'starttime']:
query['startTime'] = utils.get_date_zero_time_or_full_time(
sys.argv[i + 1])
i += 2
elif myarg in ['end', 'endtime']:
query['endTime'] = utils.get_date_zero_time_or_full_time(
sys.argv[i + 1])
i += 2
elif myarg in ['timezone']:
query['timeZone'] = sys.argv[i + 1]
i += 2
elif myarg in ['excludedrafts']:
query['mailOptions'] = {
'excludeDrafts': gam.getBoolean(sys.argv[i + 1], myarg)
}
i += 2
elif myarg in ['driveversiondate']:
query.setdefault('driveOptions', {})['versionDate'] = \
utils.get_date_zero_time_or_full_time(sys.argv[i+1])
i += 2
elif myarg in ['includeshareddrives', 'includeteamdrives']:
query.setdefault(
'driveOptions', {})['includeSharedDrives'] = gam.getBoolean(
sys.argv[i + 1], myarg)
i += 2
elif myarg in ['includerooms']:
query['hangoutsChatOptions'] = {
'includeRooms': gam.getBoolean(sys.argv[i + 1], myarg)
}
i += 2
return (query, i)
def _validate_query(query, query_discovery):
if 'corpus' not in query:
allowed_corpuses = gapi.get_enum_values_minus_unspecified(
query_discovery['properties']['corpus']['enum'])
controlflow.system_error_exit(3, 'you must specify a corpus. ' \
f'Choose one of {", ".join(allowed_corpuses)}')
if 'searchMethod' not in query:
controlflow.system_error_exit(3, f'you must specify a search method. ' \
'Choose one of ' \
f'{", ".join(VAULT_SEARCH_METHODS_LIST)}')
def createExport():
v = buildGAPIObject()
allowed_corpuses = gapi.get_enum_values_minus_unspecified(
v._rootDesc['schemas']['Query']['properties']['corpus']['enum'])
allowed_scopes = gapi.get_enum_values_minus_unspecified(
v._rootDesc['schemas']['Query']['properties']['dataScope']['enum'])
query_discovery = v._rootDesc['schemas']['Query']
allowed_formats = gapi.get_enum_values_minus_unspecified(
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': {}}
query = None
body = {'exportOptions': {}}
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
@@ -109,83 +210,8 @@ def createExport():
elif myarg == 'name':
body['name'] = sys.argv[i + 1]
i += 2
elif myarg == 'corpus':
body['query']['corpus'] = sys.argv[i + 1].upper()
if body['query']['corpus'] not in allowed_corpuses:
controlflow.expected_argument_exit('corpus',
', '.join(allowed_corpuses),
sys.argv[i + 1])
i += 2
elif myarg in VAULT_SEARCH_METHODS_MAP:
if body['query'].get('searchMethod'):
message = f'Multiple search methods ' \
f'({", ".join(VAULT_SEARCH_METHODS_LIST)})' \
f'specified, only one is allowed'
controlflow.system_error_exit(3, message)
searchMethod = VAULT_SEARCH_METHODS_MAP[myarg]
body['query']['searchMethod'] = searchMethod
if searchMethod == 'ACCOUNT':
body['query']['accountInfo'] = {
'emails': sys.argv[i + 1].split(',')
}
i += 2
elif searchMethod == 'ORG_UNIT':
body['query']['orgUnitInfo'] = {
'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
}
i += 2
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(',')
}
i += 2
else:
i += 1
elif myarg == 'scope':
body['query']['dataScope'] = sys.argv[i + 1].upper()
if body['query']['dataScope'] not in allowed_scopes:
controlflow.expected_argument_exit('scope',
', '.join(allowed_scopes),
sys.argv[i + 1])
i += 2
elif myarg in ['terms']:
body['query']['terms'] = sys.argv[i + 1]
i += 2
elif myarg in ['start', 'starttime']:
body['query']['startTime'] = utils.get_date_zero_time_or_full_time(
sys.argv[i + 1])
i += 2
elif myarg in ['end', 'endtime']:
body['query']['endTime'] = utils.get_date_zero_time_or_full_time(
sys.argv[i + 1])
i += 2
elif myarg in ['timezone']:
body['query']['timeZone'] = sys.argv[i + 1]
i += 2
elif myarg in ['excludedrafts']:
body['query']['mailOptions'] = {
'excludeDrafts': gam.getBoolean(sys.argv[i + 1], myarg)
}
i += 2
elif myarg in ['driveversiondate']:
body['query'].setdefault('driveOptions', {})['versionDate'] = \
utils.get_date_zero_time_or_full_time(sys.argv[i+1])
i += 2
elif myarg in ['includeshareddrives', 'includeteamdrives']:
body['query'].setdefault(
'driveOptions', {})['includeSharedDrives'] = gam.getBoolean(
sys.argv[i + 1], myarg)
i += 2
elif myarg in ['includerooms']:
body['query']['hangoutsChatOptions'] = {
'includeRooms': gam.getBoolean(sys.argv[i + 1], myarg)
}
i += 2
elif myarg in QUERY_ARGS:
query, i = _build_query(query, myarg, i, query_discovery)
elif myarg in ['format']:
export_format = sys.argv[i + 1].upper()
if export_format not in allowed_formats:
@@ -216,13 +242,8 @@ def createExport():
if not matterId:
controlflow.system_error_exit(
3, 'you must specify a matter for the new export.')
if 'corpus' not in body['query']:
controlflow.system_error_exit(3, f'you must specify a corpus for the ' \
f'new export. Choose one of {", ".join(allowed_corpuses)}')
if 'searchMethod' not in body['query']:
controlflow.system_error_exit(3, f'you must specify a search method ' \
'for the new export. Choose one of ' \
f'{", ".join(VAULT_SEARCH_METHODS_LIST)}')
_validate_query(query, query_discovery)
body['query'] = query
if 'name' not in body:
corpus_name = body['query']['corpus']
corpus_date = datetime.datetime.now()
@@ -270,6 +291,81 @@ def getExportInfo():
display.print_json(export)
def print_count():
v = buildGAPIObject()
query_discovery = v._rootDesc['schemas']['Query']
matterId = None
operation_wait = 15
query = None
body = {'view': 'ALL'}
name = None
todrive = False
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'matter':
matterId = getMatterItem(v, sys.argv[i + 1])
i += 2
elif myarg == 'operation':
name = sys.argv[i+1]
i += 2
elif myarg == 'todrive':
todrive = True
i += 1
elif myarg in QUERY_ARGS:
query, i = _build_query(query, myarg, i, query_discovery)
elif myarg == 'wait':
operation_wait = int(sys.argv[i + 1])
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam create export')
if not matterId:
controlflow.system_error_exit(
3, 'you must specify a matter for the count.')
if name:
operation = {'name': name}
else:
_validate_query(query, query_discovery)
body['query'] = query
operation = gapi.call(v.matters(), 'count', matterId=matterId, body=body)
print(f'Watching operation {operation["name"]}...')
while not operation.get('done'):
print(f' operation {operation["name"]} is not done yet. Checking again in {operation_wait} seconds')
sleep(operation_wait)
operation = gapi.call(v.operations(), 'get', name=operation['name'])
response = operation.get('response', {})
query = operation['metadata']['query']
search_method = query.get('searchMethod')
# ARGH count results don't include accounts with zero items.
# so we keep track of which accounts we searched and can report
# zero data for them.
if search_method == 'ACCOUNT':
query_accounts = query.get('accountInfo', [])
elif search_method == 'ENTIRE_ORG':
query_accounts = gam.getUsersToModify('all', 'users')
elif search_method == 'ORG_UNIT':
org_unit = query['orgUnitInfo']['orgUnitId']
query_accounts = gam.getUsersToModify('ou', org_unit)
mailcounts = response.get('mailCountResult', {})
groupcounts = response.get('groupsCountResult', {})
csv_rows = []
for a_count in [mailcounts, groupcounts]:
for errored_account in a_count.get('accountCountErrors', []):
account = errored_account.get('account')
csv_rows.append({'account': account, 'error': errored_account.get('errorType')})
if account in query_accounts: query_accounts.remove(account)
for account in a_count.get('nonQueryableAccounts', []):
csv_rows.append({'account': account, 'error': 'Not queried because not on hold'})
if account in query_accounts: query_accounts.remove(account)
for account in a_count.get('accountCounts', []):
email = account.get('account', {}).get('email', '')
csv_rows.append({'account': email, 'count': account.get('count')})
if email in query_accounts: query_accounts.remove(email)
for account in query_accounts:
csv_rows.append({'account': account, 'count': 0})
titles = ['account', 'count', 'error']
display.write_csv_file(csv_rows, titles, 'Vault Counts', todrive)
def createHold():
v = buildGAPIObject()
allowed_corpuses = gapi.get_enum_values_minus_unspecified(
@@ -301,7 +397,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 +503,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 +592,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

@@ -40,19 +40,19 @@ def create_http(cache=None,
return httpObj
def create_request(http=None):
def create_request(httpObj=None):
"""Creates a uniform Request object with a default http, if not provided.
Args:
http: Optional httplib2.Http compatible object to be used with the request.
httpObj: Optional httplib2.Http compatible object to be used with the request.
If not provided, a default HTTP will be used.
Returns:
Request: A google_auth_httplib2.Request compatible Request.
"""
if not http:
http = create_http()
return Request(http)
if not httpObj:
httpObj = create_http()
return Request(httpObj)
GAM_USER_AGENT = GAM_INFO

View File

@@ -64,7 +64,7 @@ class TransportTest(unittest.TestCase):
self.assertEqual(request.http, mock_create_http.return_value)
def test_create_request_uses_provided_http(self):
request = transport.create_request(http=self.mock_http)
request = transport.create_request(httpObj=self.mock_http)
self.assertEqual(request.http, self.mock_http)
def test_create_request_returns_request_with_forced_user_agent(self):

View File

@@ -1,3 +1,7 @@
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import datetime
import re
import sys
@@ -5,8 +9,10 @@ import time
from hashlib import md5
from html.entities import name2codepoint
from html.parser import HTMLParser
import importlib
import json
import dateutil.parser
import types
from gam import controlflow
from gam import fileutils
@@ -14,6 +20,41 @@ from gam import transport
from gam.var import *
class LazyLoader(types.ModuleType):
"""Lazily import a module, mainly to avoid pulling in large dependencies.
`contrib`, and `ffmpeg` are examples of modules that are large and not always
needed, and this allows them to only be loaded when they are used.
"""
# The lint error here is incorrect.
def __init__(self, local_name, parent_module_globals, name): # pylint: disable=super-on-old-class
self._local_name = local_name
self._parent_module_globals = parent_module_globals
super(LazyLoader, self).__init__(name)
def _load(self):
# Import the target module and insert it into the parent's namespace
module = importlib.import_module(self.__name__)
self._parent_module_globals[self._local_name] = module
# Update this object's dict so that if someone keeps a reference to the
# LazyLoader, lookups are efficient (__getattr__ is only called on lookups
# that fail).
self.__dict__.update(module.__dict__)
return module
def __getattr__(self, item):
module = self._load()
return getattr(module, item)
def __dir__(self):
module = self._load()
return dir(module)
class _DeHTMLParser(HTMLParser):
def __init__(self):
@@ -59,6 +100,16 @@ class _DeHTMLParser(HTMLParser):
re.sub(r'\n +', '\n', ''.join(self.__text))).strip()
def commonprefix(m):
'''Given a list of strings m, return string which is prefix common to all'''
s1 = min(m)
s2 = max(m)
for i, c in enumerate(s1):
if c != s2[i]:
return s1[:i]
return s1
def dehtml(text):
try:
parser = _DeHTMLParser()
@@ -228,12 +279,14 @@ def get_yyyymmdd(argstr, minLen=1, returnTimeStamp=False, returnDateTime=False):
def get_time_or_delta_from_now(time_string):
"""Get an ISO 8601 time or a positive/negative delta applied to now.
Args:
time_string (string): The time or delta (e.g. '2017-09-01T12:34:56Z' or '-4h')
time_string (string): The time or delta (e.g. '2017-09-01T12:34:56Z' or '-4h') or never
Returns:
string: iso8601 formatted datetime in UTC.
"""
time_string = time_string.strip().upper()
if time_string:
if time_string == 'NEVER':
return NEVER_TIME
if time_string[0] not in ['+', '-']:
return time_string
return (datetime.datetime.utcnow() +

View File

@@ -8,7 +8,7 @@ import platform
import re
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
GAM_VERSION = '5.24'
GAM_VERSION = '6.05'
GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
GAM_URL = 'https://git.io/gam'
@@ -56,15 +56,50 @@ SKUS = {
'aliases': ['identitypremium', 'cloudidentitypremium'],
'displayName': 'Cloud Identity Premium'
},
'1010350001': {
'product': '101035',
'aliases': ['cloudsearch'],
'displayName': 'Google Cloud Search',
},
'1010310002': {
'product': '101031',
'aliases': ['gsefe', 'e4e', 'gsuiteenterpriseeducation'],
'displayName': 'G Suite Enterprise for Education'
'displayName': 'Google Workspace for Education Plus - Legacy'
},
'1010310003': {
'product': '101031',
'aliases': ['gsefes', 'e4es', 'gsuiteenterpriseeducationstudent'],
'displayName': 'G Suite Enterprise for Education (Student)'
'displayName': 'Google Workspace for Education Plus - Legacy (Student)'
},
'1010310005': {
'product': '101031',
'aliases': ['gwes', 'workspaceeducationstandard'],
'displayName': 'Google Workspace for Education Standard'
},
'1010310006': {
'product': '101031',
'aliases': ['gwesstaff', 'workspaceeducationstandardstaff'],
'displayName': 'Google Workspace for Education Standard (Staff)'
},
'1010310007': {
'product': '101031',
'aliases': ['gwesstudent', 'workspaceeducationstandardstudent'],
'displayName': 'Google Workspace for Education Standard (Extra Student)'
},
'1010310008': {
'product': '101031',
'aliases': ['gwep', 'workspaceeducationplus'],
'displayName': 'Google Workspace for Education Plus'
},
'1010310009': {
'product': '101031',
'aliases': ['gwepstaff', 'workspaceeducationplusstaff'],
'displayName': 'Google Workspace for Education Plus (Staff)'
},
'1010310010': {
'product': '101031',
'aliases': ['gwepstudent', 'workspaceeducationplusstudent'],
'displayName': 'Google Workspace for Education Plus (Extra Student)'
},
'1010330003': {
'product': '101033',
@@ -81,6 +116,11 @@ SKUS = {
'aliases': ['gvpremier', 'voicepremier', 'googlevoicepremier'],
'displayName': 'Google Voice Premier'
},
'1010370001': {
'product': '101037',
'aliases': ['gwetlu', 'workspaceeducationupgrade'],
'displayName': 'Google Workspace for Education: Teaching and Learning Upgrade'
},
'Google-Apps': {
'product': 'Google-Apps',
'aliases': ['standard', 'free'],
@@ -153,6 +193,11 @@ SKUS = {
'wsentplus', 'workspaceenterpriseplus'],
'displayName': 'Workspace Enterprise Plus'
},
'1010020030': {
'product': 'Google-Apps',
'aliases': ['workspacefrontline', 'workspacefrontlineworker'],
'displayName': 'Workspace Frontline'
},
'1010340002': {
'product': '101034',
'aliases': ['gsbau', 'businessarchived', 'gsuitebusinessarchived'],
@@ -228,12 +273,13 @@ SKUS = {
PRODUCTID_NAME_MAPPINGS = {
'101001': 'Cloud Identity Free',
'101005': 'Cloud Identity Premium',
'101031': 'G Suite Enterprise for Education',
'101031': 'G Suite Workspace for Education',
'101033': 'Google Voice',
'101034': 'G Suite Archived',
'101035': 'Cloud Search',
'101037': 'G Suite Workspace for Education',
'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 +287,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,13 +305,17 @@ API_NAME_MAPPING = {
API_VER_MAPPING = {
'alertcenter': 'v1beta1',
'appsactivity': 'v1',
'driveactivity': 'v2',
'calendar': 'v3',
'cbcm': 'v1.1beta1',
'chromemanagement': 'v1',
'chromepolicy': 'v1',
'classroom': 'v1',
'cloudidentity': 'v1',
'cloudidentity_beta': 'v1beta1',
'cloudresourcemanager': 'v2',
'cloudresourcemanagerv1': 'v1',
'contactdelegation': 'v1',
'datatransfer': 'datatransfer_v1',
'directory': 'directory_v1',
'drive': 'v2',
@@ -286,18 +335,20 @@ API_VER_MAPPING = {
'siteVerification': 'v1',
'storage': 'v1',
'vault': 'v1',
'versionhistory': 'v1',
}
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',],
'cloudidentity': ['https://www.googleapis.com/auth/cloud-identity',],
'cloudidentity': ['https://www.googleapis.com/auth/cloud-identity'],
'cloudidentity_beta': ['https://www.googleapis.com/auth/cloud-identity'],
'drive': ['https://www.googleapis.com/auth/drive',],
'drive3': ['https://www.googleapis.com/auth/drive',],
'gmail': [
@@ -344,16 +395,21 @@ ADDRESS_FIELDS_ARGUMENT_MAP = {
}
SERVICE_NAME_TO_ID_MAP = {
'Calendar': '435070579839',
'Currents': '553547912911',
'Drive and Docs': '55656082996',
'Calendar': '435070579839'
'Google Data Studio': '810260081642',
}
SERVICE_NAME_CHOICES_MAP = {
'calendar': 'Calendar',
'currents': 'Currents',
'datastudio': 'Google Data Studio',
'google data studio': 'Google Data Studio',
'drive': 'Drive and Docs',
'drive and docs': 'Drive and Docs',
'googledrive': 'Drive and Docs',
'gdrive': 'Drive and Docs',
'calendar': 'Calendar',
}
PRINTJOB_ASCENDINGORDER_MAP = {
@@ -906,6 +962,7 @@ CROS_ARGUMENT_TO_PROPERTY_MAP = {
'ethernetmacaddress0': ['ethernetMacAddress0',],
'firmwareversion': ['firmwareVersion',],
'lastenrollmenttime': ['lastEnrollmentTime',],
'lastknownnetwork': ['lastKnownNetwork'],
'lastsync': ['lastSync',],
'location': ['annotatedLocation',],
'macaddress': ['macAddress',],
@@ -1142,7 +1199,7 @@ GM_Globals = {
GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID: None,
GM_ENABLEDASA_TXT: '',
GM_LAST_UPDATE_CHECK_TXT: '',
GM_MAP_ORGUNIT_ID_TO_NAME: None,
GM_MAP_ORGUNIT_ID_TO_NAME: {},
GM_MAP_ROLE_ID_TO_NAME: None,
GM_MAP_ROLE_NAME_TO_ID: None,
GM_MAP_USER_ID_TO_NAME: None,
@@ -1220,6 +1277,8 @@ GC_CSV_HEADER_FILTER = 'csv_header_filter'
GC_CSV_HEADER_DROP_FILTER = 'csv_header_drop_filter'
# CSV Rows GAM should filter
GC_CSV_ROW_FILTER = 'csv_row_filter'
# CSV Rows GAM should filter/drop
GC_CSV_ROW_DROP_FILTER = 'csv_row_drop_filter'
# Minimum TLS Version required for HTTPS connections
GC_TLS_MIN_VERSION = 'tls_min_ver'
# Maximum TLS Version used for HTTPS connections
@@ -1258,6 +1317,7 @@ GC_Defaults = {
GC_CSV_HEADER_FILTER: '',
GC_CSV_HEADER_DROP_FILTER: '',
GC_CSV_ROW_FILTER: '',
GC_CSV_ROW_DROP_FILTER: '',
GC_TLS_MIN_VERSION: TLS_MIN,
GC_TLS_MAX_VERSION: None,
GC_CA_FILE: None,
@@ -1372,6 +1432,9 @@ GC_VAR_INFO = {
GC_CSV_ROW_FILTER: {
GC_VAR_TYPE: GC_TYPE_ROWFILTER
},
GC_CSV_ROW_DROP_FILTER: {
GC_VAR_TYPE: GC_TYPE_ROWFILTER
},
GC_TLS_MIN_VERSION: {
GC_VAR_TYPE: GC_TYPE_STRING
},
@@ -1457,7 +1520,7 @@ USER_EXTERNALID_TYPES = [
]
USER_GENDER_TYPES = ['female', 'male', 'unknown']
USER_IM_TYPES = ['home', 'work', 'other']
USER_KEYWORD_TYPES = ['occupation', 'outlook']
USER_KEYWORD_TYPES = ['occupation', 'outlook', 'mission']
USER_LOCATION_TYPES = ['default', 'desk']
USER_ORGANIZATION_TYPES = ['domain_only', 'school', 'unknown', 'work']
USER_PHONE_TYPES = [
@@ -1472,7 +1535,7 @@ USER_RELATION_TYPES = [
]
USER_WEBSITE_TYPES = [
'app_install_page', 'blog', 'ftp', 'home', 'home_page', 'other', 'profile',
'reservations', 'work'
'reservations', 'resume', 'work'
]
WEBCOLOR_MAP = {

View File

@@ -1,12 +1,14 @@
admin.googleapis.com
alertcenter.googleapis.com
appsactivity.googleapis.com
calendar-json.googleapis.com
chat.googleapis.com
chromemanagement.googleapis.com
chromepolicy.googleapis.com
classroom.googleapis.com
cloudidentity.googleapis.com
contacts.googleapis.com
drive.googleapis.com
driveactivity.googleapis.com
iap.googleapis.com
gmail.googleapis.com
groupssettings.googleapis.com

View File

@@ -1,10 +1,12 @@
cryptography
distro; sys_platform == 'linux'
filelock
google-api-python-client>=1.7.10
google-api-python-client>=2.1
google-auth-httplib2
google-auth-oauthlib>=0.4.1
google-auth>=1.11.2
httplib2>=0.17.0
passlib>=1.7.2; sys_platform == 'win32'
importlib.metadata; python_version < '3.8'
passlib>=1.7.2
python-dateutil
yubikey-manager>=4.0.0

Binary file not shown.

View File

@@ -1,38 +0,0 @@
cd src
if [[ "$TRAVIS_JOB_NAME" == *"Testing" ]]; then
export gam="$python -m gam"
export gampath=$(readlink -e .)
else
export gampath="dist/gam"
rm -rf $gampath
mkdir -p $gampath
export gampath=$(readlink -e $gampath)
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath $gampath gam.spec
export gam="${gampath}/gam"
export GAMVERSION=`$gam version simple`
cp LICENSE $gampath
cp GamCommands.txt $gampath
this_glibc_ver=$(ldd --version | awk '/ldd/{print $NF}')
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM-glibc$this_glibc_ver.tar.xz
rm $gampath/lastupdatecheck.txt
# tar will cd to dist and tar up gam/
tar -C dist/ --create --file $GAM_ARCHIVE --xz gam
echo "PyInstaller GAM info:"
du -h $gam
time $gam version extended
if [ "${TRAVIS_DIST}" == "xenial" ] && [ "${PLATFORM}" == "x86_64" ]; then
GAM_LEGACY_ARCHIVE=gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-legacy.tar.xz
$python -OO -m staticx -l /lib/x86_64-linux-gnu/libresolv.so.2 -l /lib/x86_64-linux-gnu/libnss_dns.so.2 $gam $gam-staticx
strip $gam-staticx
rm $gampath/gam
mv $gam-staticx $gam
chmod 755 $gam
rm $gampath/lastupdatecheck.txt
tar -C dist/ --create --file $GAM_LEGACY_ARCHIVE --xz gam
echo "Legacy StaticX GAM info:"
du -h $gam
time $gam version extended
fi
echo "GAM packages:"
ls -l gam-*.tar.xz
fi

View File

@@ -1,111 +0,0 @@
mypath=$HOME
whereibelong=$(pwd)
cpucount=$(sysctl -n hw.ncpu)
echo "This device has $cpucount CPUs for compiling..."
#echo "Brew installing xz..."
#brew install xz > /dev/null
#brew upgrade
# prefer standard GNU tools like date over MacOS defaults
export PATH="/usr/local/opt/coreutils/libexec/gnubin:$(brew --prefix)/opt/gnu-tar/libexec/gnubin:$PATH"
cd ~
#if [ ! -f python-$MIN_PYTHON_VERSION-macosx10.9.pkg ]; then
# wget --quiet https://www.python.org/ftp/python/$MIN_PYTHON_VERSION/python-$MIN_PYTHON_VERSION-macosx10.9.pkg
#fi
#sudo installer -pkg python-$MIN_PYTHON_VERSION-macosx10.9.pkg -target /
#brew install openssl@1.1
#brew upgrade python
#export python=python3
#export pip=pip3
#echo "Python location:"
#which $python
cd ~
export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
export openssl=~/ssl/bin/openssl
export python=~/python/bin/python3
export pip=~/python/bin/pip3
SSLVER=$($openssl version)
SSLRESULT=$?
PYVER=$($python -V)
PYRESULT=$?
if [ $SSLRESULT -ne 0 ] || [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]] || [ $PYRESULT -ne 0 ] || [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION"* ]]; then
echo "SSL Result: $SSLRESULT - SSL Ver: $SSLVER - Py Result: $PYRESULT - Py Ver: $PYVER"
if [ $SSLRESULT -ne 0 ]; then
echo "sslresult -ne 0"
fi
if [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]]; then
echo "sslver not equal to..."
fi
if [ $PYRESULT -ne 0 ]; then
echo "pyresult -ne 0"
fi
if [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION" ]]; then
echo "pyver not equal to..."
fi
# Start clean
rm -rf python
rm -rf ssl
mkdir python
mkdir ssl
# Compile latest OpenSSL
wget --quiet https://www.openssl.org/source/openssl-$BUILD_OPENSSL_VERSION.tar.gz
echo "Extracting OpenSSL..."
tar xf openssl-$BUILD_OPENSSL_VERSION.tar.gz
cd openssl-$BUILD_OPENSSL_VERSION
echo "Compiling OpenSSL $BUILD_OPENSSL_VERSION..."
./config shared --prefix=$HOME/ssl
echo "Running make for OpenSSL..."
make -j$cpucount -s
echo "Running make install for OpenSSL..."
make install > /dev/null
cd ~
# Compile latest Python
echo "Downloading Python $BUILD_PYTHON_VERSION..."
curl -O https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/Python-$BUILD_PYTHON_VERSION.tar.xz
echo "Extracting Python..."
tar xf Python-$BUILD_PYTHON_VERSION.tar.xz
cd Python-$BUILD_PYTHON_VERSION
echo "Compiling Python $BUILD_PYTHON_VERSION..."
safe_flags="--with-openssl=$HOME/ssl --enable-shared --prefix=$HOME/python --with-ensurepip=upgrade"
unsafe_flags="--enable-optimizations --with-lto"
if [ ! -e Makefile ]; then
echo "running configure with safe and unsafe"
./configure $safe_flags $unsafe_flags > /dev/null
fi
make -j$cpucount PROFILE_TASK="-m test.regrtest --pgo -j$(( $cpucount * 2 ))" -s
RESULT=$?
echo "First make exited with $RESULT"
if [ $RESULT != 0 ]; then
echo "Trying Python compile again without unsafe flags..."
make clean
./configure $safe_flags > /dev/null
make -j$cpucount -s
echo "Sticking with safe Python for now..."
fi
echo "Installing Python..."
make install > /dev/null
cd ~
fi
$python -V
cd $whereibelong
#export PATH=/usr/local/opt/python/libexec/bin:$PATH
$pip install --upgrade pip
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
$pip install --upgrade -r src/requirements.txt
$pip install --upgrade git+git://github.com/pyinstaller/pyinstaller.git@$PYINSTALLER_COMMIT

View File

@@ -1,18 +0,0 @@
cd src
echo "MacOS Version Info According to Python:"
python -c "import platform; print(platform.mac_ver())"
echo "Xcode versionn:"
xcodebuild -version
export gampath=dist/gam
rm -rf $gampath
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath $gampath gam.spec
export gam="$gampath/gam"
$gam version extended
export GAMVERSION=`$gam version simple`
cp LICENSE $gampath
cp GamCommands.txt $gampath
MACOSVERSION=$(defaults read loginwindow SystemVersionStampAsString)
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM-MacOS$MACOSVERSION.tar.xz
rm $gampath/lastupdatecheck.txt
# tar will cd to dist/ and tar up gam/
tar -C dist/ --create --file $GAM_ARCHIVE --xz gam

View File

@@ -1,82 +0,0 @@
if [[ "$PLATFORM" == "x86_64" ]]; then
export BITS="64"
export PYTHONFILE_BITS="-amd64"
export OPENSSL_BITS="-x64"
export WIX_BITS="x64"
elif [[ "$PLATFORM" == "x86" ]]; then
export BITS="32"
export PYTHONFILE_BITS=""
export OPENSSL_BITS=""
export WIX_BITS="x86"
fi
echo "This is a ${BITS}-bit build for ${PLATFORM}"
export mypath=$(pwd)
cd ~
# .NET Core
echo "Installing Net-Framework-Core..."
until powershell Install-WindowsFeature Net-Framework-Core; do echo "trying .net again..."; done
# VS 2015
echo "Installing Visual Studio 2015.."
until choco install vcbuildtools; do echo "Trying Visual Studio again..."; done
# Python
echo "Installing Python..."
export python_file=python-${BUILD_PYTHON_VERSION}${PYTHONFILE_BITS}.exe
if [ ! -e $python_file ]; then
echo "Downloading $python_file..."
wget --quiet https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/$python_file
fi
until powershell ".\\${python_file} /quiet InstallAllUsers=1 TargetDir=c:\\python"; do echo "trying python again..."; done
export python=/c/python/python.exe
export pip=/c/python/scripts/pip.exe
until [ -f $python ]; do sleep 1; done
export PATH=$PATH:/c/python/scripts
# OpenSSL
echo "Installing OpenSSL..."
export exefile=Win${BITS}OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
if [ ! -e $exefile ]; then
echo "Downloading $exefile..."
wget --quiet https://slproweb.com/download/$exefile
fi
until powershell ".\\${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl"; do echo "trying openssl again..."; done
until cp -v /c/ssl/libcrypto-1_1${OPENSSL_BITS}.dll /c/python/DLLs/; do echo "trying libcrypto copy again..."; sleep 3; done
until cp -v /c/ssl/libssl-1_1${OPENSSL_BITS}.dll /c/python/DLLs/; do echo "trying libssl copy again..."; done
if [[ "$PLATFORM" == "x86_64" ]]; then
cp -v /c/python/DLLs/libssl-1_1-x64.dll /c/python/DLLs/libssl-1_1.dll
cp -v /c/python/DLLs/libcrypto-1_1-x64.dll /c/python/DLLs/libcrypto-1_1.dll
fi
# WIX Toolset
until cinst -y wixtoolset; do echo "trying wix install again..."; done
cd $mypath
$pip install --upgrade pip
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
$pip install --upgrade -r src/requirements.txt
#$pip install --upgrade pyinstaller
# Install PyInstaller from source and build bootloader
# to try and avoid getting flagged as malware since
# lots of malware uses PyInstaller default bootloader
# https://stackoverflow.com/questions/53584395/how-to-recompile-the-bootloader-of-pyinstaller
echo "Downloading PyInstaller..."
wget --quiet https://github.com/pyinstaller/pyinstaller/archive/$PYINSTALLER_COMMIT.tar.gz
tar xf $PYINSTALLER_COMMIT.tar.gz
mv pyinstaller-$PYINSTALLER_COMMIT pyinstaller
cd pyinstaller/bootloader
echo "bootloader before:"
md5sum ../PyInstaller/bootloader/Windows-${BITS}bit/*
$python ./waf all --target-arch=${BITS}bit --msvc_version "msvc 14.0"
echo "bootloader after:"
md5sum ../PyInstaller/bootloader/Windows-${BITS}bit/*
echo "PATH: $PATH"
cd ..
$python setup.py install
echo "cd to $mypath"
cd $mypath

486
src/versionhistory-v1.json Normal file
View File

@@ -0,0 +1,486 @@
{
"revision": "20210322",
"name": "versionhistory",
"mtlsRootUrl": "https://versionhistory.mtls.googleapis.com/",
"version_module": true,
"basePath": "",
"title": "Version History API",
"kind": "discovery#restDescription",
"servicePath": "",
"ownerDomain": "google.com",
"parameters": {
"access_token": {
"location": "query",
"description": "OAuth access token.",
"type": "string"
},
"alt": {
"default": "json",
"location": "query",
"enum": [
"json",
"media",
"proto"
],
"type": "string",
"enumDescriptions": [
"Responses with Content-Type of application/json",
"Media download with context-dependent Content-Type",
"Responses with Content-Type of application/x-protobuf"
],
"description": "Data format for response."
},
"quotaUser": {
"type": "string",
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
"location": "query"
},
"$.xgafv": {
"location": "query",
"enumDescriptions": [
"v1 error format",
"v2 error format"
],
"enum": [
"1",
"2"
],
"description": "V1 error format.",
"type": "string"
},
"fields": {
"type": "string",
"description": "Selector specifying which fields to include in a partial response.",
"location": "query"
},
"key": {
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
"location": "query",
"type": "string"
},
"callback": {
"location": "query",
"description": "JSONP",
"type": "string"
},
"oauth_token": {
"description": "OAuth 2.0 token for the current user.",
"location": "query",
"type": "string"
},
"upload_protocol": {
"location": "query",
"type": "string",
"description": "Upload protocol for media (e.g. \"raw\", \"multipart\")."
},
"prettyPrint": {
"description": "Returns response with indentations and line breaks.",
"default": "true",
"location": "query",
"type": "boolean"
},
"uploadType": {
"description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
"type": "string",
"location": "query"
}
},
"ownerName": "Google",
"protocol": "rest",
"resources": {
"platforms": {
"methods": {
"list": {
"httpMethod": "GET",
"parameterOrder": [
"parent"
],
"response": {
"$ref": "ListPlatformsResponse"
},
"path": "v1/{+parent}/platforms",
"description": "Returns list of platforms that are avaialble for a given product. The resource \"product\" has no resource name in its name.",
"flatPath": "v1/{v1Id}/platforms",
"id": "versionhistory.platforms.list",
"parameters": {
"parent": {
"location": "path",
"pattern": "^[^/]+$",
"type": "string",
"required": true,
"description": "Required. The product, which owns this collection of platforms. Format: {product}"
},
"pageSize": {
"format": "int32",
"description": "Optional. Optional limit on the number of channels to include in the response. If unspecified, the server will pick an appropriate default.",
"type": "integer",
"location": "query"
},
"pageToken": {
"location": "query",
"description": "Optional. A page token, received from a previous `ListChannels` call. Provide this to retrieve the subsequent page.",
"type": "string"
}
}
}
},
"resources": {
"channels": {
"resources": {
"versions": {
"resources": {
"releases": {
"methods": {
"list": {
"id": "versionhistory.platforms.channels.versions.releases.list",
"path": "v1/{+parent}/releases",
"httpMethod": "GET",
"parameterOrder": [
"parent"
],
"response": {
"$ref": "ListReleasesResponse"
},
"flatPath": "v1/{v1Id}/platforms/{platformsId}/channels/{channelsId}/versions/{versionsId}/releases",
"parameters": {
"parent": {
"type": "string",
"required": true,
"description": "Required. The version, which owns this collection of releases. Format: {product}/platforms/{platform}/channels/{channel}/versions/{version}",
"pattern": "^[^/]+/platforms/[^/]+/channels/[^/]+/versions/[^/]+$",
"location": "path"
},
"filter": {
"type": "string",
"location": "query",
"description": "Optional. Filter string. Format is a comma separated list of All comma separated filter clauses are conjoined with a logical \"and\". Valid field_names are \"version\", \"name\", \"platform\", \"channel\", \"fraction\" \"starttime\", and \"endtime\". Valid operators are \"\u003c\", \"\u003c=\", \"=\", \"\u003e=\", and \"\u003e\". Channel comparison is done by distance from stable. must be a valid channel when filtering by channel. Ex) stable \u003c beta, beta \u003c dev, canary \u003c canary_asan. Version comparison is done numerically. Ex) 1.0.0.8 \u003c 1.0.0.10. If version is not entirely written, the version will be appended with 0 for the missing fields. Ex) version \u003e 80 becoms version \u003e 80.0.0.0 When filtering by starttime or endtime, string must be in RFC 3339 date string format. Name and platform are filtered by string comparison. Ex) \"...?filter=channel\u003c=beta, version \u003e= 80 Ex) \"...?filter=version \u003e 80, version \u003c 81 Ex) \"...?filter=starttime\u003e2020-01-01T00:00:00Z"
},
"orderBy": {
"location": "query",
"description": "Optional. Ordering string. Valid order_by strings are \"version\", \"name\", \"starttime\", \"endtime\", \"platform\", \"channel\", and \"fraction\". Optionally, you can append \"desc\" or \"asc\" to specify the sorting order. Multiple order_by strings can be used in a comma separated list. Ordering by channel will sort by distance from the stable channel (not alphabetically). A list of channels sorted in this order is: stable, beta, dev, canary, and canary_asan. Sorting by name may cause unexpected behaviour as it is a naive string sort. For example, 1.0.0.8 will be before 1.0.0.10 in descending order. If order_by is not specified the response will be sorted by starttime in descending order. Ex) \"...?order_by=starttime asc\" Ex) \"...?order_by=platform desc, channel, startime desc\"",
"type": "string"
},
"pageSize": {
"location": "query",
"format": "int32",
"description": "Optional. Optional limit on the number of releases to include in the response. If unspecified, the server will pick an appropriate default.",
"type": "integer"
},
"pageToken": {
"description": "Optional. A page token, received from a previous `ListReleases` call. Provide this to retrieve the subsequent page.",
"location": "query",
"type": "string"
}
},
"description": "Returns list of releases of the given version."
}
}
}
},
"methods": {
"list": {
"response": {
"$ref": "ListVersionsResponse"
},
"path": "v1/{+parent}/versions",
"parameters": {
"pageSize": {
"location": "query",
"format": "int32",
"description": "Optional. Optional limit on the number of versions to include in the response. If unspecified, the server will pick an appropriate default.",
"type": "integer"
},
"pageToken": {
"description": "Optional. A page token, received from a previous `ListVersions` call. Provide this to retrieve the subsequent page.",
"location": "query",
"type": "string"
},
"parent": {
"required": true,
"location": "path",
"description": "Required. The channel, which owns this collection of versions. Format: {product}/platforms/{platform}/channels/{channel}",
"pattern": "^[^/]+/platforms/[^/]+/channels/[^/]+$",
"type": "string"
},
"orderBy": {
"type": "string",
"location": "query",
"description": "Optional. Ordering string. Valid order_by strings are \"version\", \"name\", \"platform\", and \"channel\". Optionally, you can append \" desc\" or \" asc\" to specify the sorting order. Multiple order_by strings can be used in a comma separated list. Ordering by channel will sort by distance from the stable channel (not alphabetically). A list of channels sorted in this order is: stable, beta, dev, canary, and canary_asan. Sorting by name may cause unexpected behaviour as it is a naive string sort. For example, 1.0.0.8 will be before 1.0.0.10 in descending order. If order_by is not specified the response will be sorted by version in descending order. Ex) \"...?order_by=version asc\" Ex) \"...?order_by=platform desc, channel, version\""
},
"filter": {
"description": "Optional. Filter string. Format is a comma separated list of All comma separated filter clauses are conjoined with a logical \"and\". Valid field_names are \"version\", \"name\", \"platform\", and \"channel\". Valid operators are \"\u003c\", \"\u003c=\", \"=\", \"\u003e=\", and \"\u003e\". Channel comparison is done by distance from stable. Ex) stable \u003c beta, beta \u003c dev, canary \u003c canary_asan. Version comparison is done numerically. If version is not entirely written, the version will be appended with 0 in missing fields. Ex) version \u003e 80 becoms version \u003e 80.0.0.0 Name and platform are filtered by string comparison. Ex) \"...?filter=channel\u003c=beta, version \u003e= 80 Ex) \"...?filter=version \u003e 80, version \u003c 81",
"location": "query",
"type": "string"
}
},
"id": "versionhistory.platforms.channels.versions.list",
"parameterOrder": [
"parent"
],
"description": "Returns list of version for the given platform/channel.",
"flatPath": "v1/{v1Id}/platforms/{platformsId}/channels/{channelsId}/versions",
"httpMethod": "GET"
}
}
}
},
"methods": {
"list": {
"response": {
"$ref": "ListChannelsResponse"
},
"parameterOrder": [
"parent"
],
"parameters": {
"pageToken": {
"type": "string",
"location": "query",
"description": "Optional. A page token, received from a previous `ListChannels` call. Provide this to retrieve the subsequent page."
},
"parent": {
"location": "path",
"type": "string",
"required": true,
"pattern": "^[^/]+/platforms/[^/]+$",
"description": "Required. The platform, which owns this collection of channels. Format: {product}/platforms/{platform}"
},
"pageSize": {
"format": "int32",
"type": "integer",
"description": "Optional. Optional limit on the number of channels to include in the response. If unspecified, the server will pick an appropriate default.",
"location": "query"
}
},
"path": "v1/{+parent}/channels",
"httpMethod": "GET",
"flatPath": "v1/{v1Id}/platforms/{platformsId}/channels",
"id": "versionhistory.platforms.channels.list",
"description": "Returns list of channels that are available for a given platform."
}
}
}
}
}
},
"description": "Version History API - Prod",
"discoveryVersion": "v1",
"schemas": {
"Channel": {
"id": "Channel",
"type": "object",
"description": "Each Channel is owned by a Platform and owns a collection of versions. Possible Channels are listed in the Channel enum below. Not all Channels are available for every Platform (e.g. CANARY does not exist for LINUX).",
"properties": {
"name": {
"description": "Channel name. Format is \"{product}/platforms/{platform}/channels/{channel}\"",
"type": "string"
},
"channelType": {
"description": "Type of channel.",
"enumDescriptions": [
"",
"",
"",
"",
"",
"",
"",
""
],
"type": "string",
"enum": [
"CHANNEL_TYPE_UNSPECIFIED",
"STABLE",
"BETA",
"DEV",
"CANARY",
"CANARY_ASAN",
"ALL",
"EXTENDED"
]
}
}
},
"Interval": {
"description": "Represents a time interval, encoded as a Timestamp start (inclusive) and a Timestamp end (exclusive). The start must be less than or equal to the end. When the start equals the end, the interval is empty (matches no time). When both start and end are unspecified, the interval matches any time.",
"type": "object",
"id": "Interval",
"properties": {
"endTime": {
"type": "string",
"format": "google-datetime",
"description": "Optional. Exclusive end of the interval. If specified, a Timestamp matching this interval will have to be before the end."
},
"startTime": {
"format": "google-datetime",
"type": "string",
"description": "Optional. Inclusive start of the interval. If specified, a Timestamp matching this interval will have to be the same or after the start."
}
}
},
"ListVersionsResponse": {
"description": "Response message for ListVersions.",
"type": "object",
"properties": {
"versions": {
"type": "array",
"description": "The list of versions.",
"items": {
"$ref": "Version"
}
},
"nextPageToken": {
"description": "A token, which can be sent as `page_token` to retrieve the next page. If this field is omitted, there are no subsequent pages.",
"type": "string"
}
},
"id": "ListVersionsResponse"
},
"ListChannelsResponse": {
"description": "Response message for ListChannels.",
"id": "ListChannelsResponse",
"type": "object",
"properties": {
"nextPageToken": {
"description": "A token, which can be sent as `page_token` to retrieve the next page. If this field is omitted, there are no subsequent pages.",
"type": "string"
},
"channels": {
"description": "The list of channels.",
"type": "array",
"items": {
"$ref": "Channel"
}
}
}
},
"Platform": {
"properties": {
"platformType": {
"enum": [
"PLATFORM_TYPE_UNSPECIFIED",
"WIN",
"WIN64",
"MAC",
"LINUX",
"ANDROID",
"WEBVIEW",
"IOS",
"ALL",
"MAC_ARM64",
"LACROS"
],
"description": "Type of platform.",
"enumDescriptions": [
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
""
],
"type": "string"
},
"name": {
"description": "Platform name. Format is \"{product}/platforms/{platform}\"",
"type": "string"
}
},
"id": "Platform",
"type": "object",
"description": "Each Platform is owned by a Product and owns a collection of channels. Available platforms are listed in Platform enum below. Not all Channels are available for every Platform (e.g. CANARY does not exist for LINUX)."
},
"Version": {
"properties": {
"version": {
"description": "String containing just the version number. e.g. \"84.0.4147.38\"",
"type": "string"
},
"name": {
"description": "Version name. Format is \"{product}/platforms/{platform}/channels/{channel}/versions/{version}\" e.g. \"chrome/platforms/win/channels/beta/versions/84.0.4147.38\"",
"type": "string"
}
},
"id": "Version",
"type": "object",
"description": "Each Version is owned by a Channel. A Version only displays the Version number (e.g. 84.0.4147.38). A Version owns a collection of releases."
},
"ListPlatformsResponse": {
"description": "Response message for ListPlatforms.",
"id": "ListPlatformsResponse",
"type": "object",
"properties": {
"nextPageToken": {
"type": "string",
"description": "A token, which can be sent as `page_token` to retrieve the next page. If this field is omitted, there are no subsequent pages."
},
"platforms": {
"type": "array",
"items": {
"$ref": "Platform"
},
"description": "The list of platforms."
}
}
},
"ListReleasesResponse": {
"type": "object",
"id": "ListReleasesResponse",
"description": "Response message for ListReleases.",
"properties": {
"nextPageToken": {
"description": "A token, which can be sent as `page_token` to retrieve the next page. If this field is omitted, there are no subsequent pages.",
"type": "string"
},
"releases": {
"description": "The list of releases.",
"items": {
"$ref": "Release"
},
"type": "array"
}
}
},
"Release": {
"properties": {
"fraction": {
"format": "double",
"type": "number",
"description": "Rollout fraction. This fraction indicates the fraction of people that should receive this version in this release. If the fraction is not specified in ReleaseManager, the API will assume fraction is 1."
},
"version": {
"type": "string",
"description": "String containing just the version number. e.g. \"84.0.4147.38\""
},
"name": {
"type": "string",
"description": "Release name. Format is \"{product}/platforms/{platform}/channels/{channel}/versions/{version}/releases/{release}\""
},
"serving": {
"description": "Timestamp interval of when the release was live. If end_time is unspecified, the release is currently live.",
"$ref": "Interval"
}
},
"type": "object",
"description": "A Release is owned by a Version. A Release contains information about the release(s) of its parent version. This includes when the release began and ended, as well as what percentage it was released at. If the version is released again, or if the serving percentage changes, it will create another release under the version.",
"id": "Release"
}
},
"fullyEncodeReservedExpansion": true,
"documentationLink": "https://developers.chrome.com/",
"icons": {
"x16": "http://www.google.com/images/icons/product/search-16.gif",
"x32": "http://www.google.com/images/icons/product/search-32.gif"
},
"baseUrl": "https://versionhistory.googleapis.com/",
"batchPath": "batch",
"version": "v1",
"canonicalName": "Version History",
"id": "versionhistory:v1",
"rootUrl": "https://versionhistory.googleapis.com/"
}