Compare commits

...

232 Commits
v6.16 ... v6.23

Author SHA1 Message Date
Jay Lee
6f6ccad00b further refine gam checkconn 2022-08-22 15:16:40 +00:00
Ross Scroggs
bd18f14137 checkconnection cleanup (#1552) 2022-08-22 09:19:39 -04:00
Jay Lee
d54ca7ee43 Update build.yml 2022-08-20 09:31:46 -04:00
Jay Lee
19452c2461 Fix info customer 2022-08-20 13:10:23 +00:00
Jay Lee
4e2e96a6dd Update build.yml 2022-08-20 08:54:15 -04:00
Jay Lee
7957d131c0 Merge branch 'main' of https://github.com/GAM-team/GAM 2022-08-19 20:37:32 +00:00
Jay Lee
ca9dfaff1d gam checkconn first shot 2022-08-19 20:33:19 +00:00
Ross Scroggs
7e9475791b Handle Reports API bug (#1550)
* Handle Reports API bug

* Handle Reports API bug
2022-08-18 19:07:17 -04:00
Jay Lee
c8fb44a7c4 Update build.yml 2022-08-10 19:42:57 -04:00
Jay Lee
bb70183bc7 Update build.yml 2022-08-10 09:11:14 -04:00
Jay Lee
ff80ba1814 Update build.yml 2022-08-09 10:52:51 -04:00
Jay Lee
5d292dcaf7 Update var.py 2022-08-04 14:34:49 -04:00
Jay Lee
bcc5c4520f Update build.yml 2022-08-04 13:44:48 -04:00
Jay Lee
aa7ea59b5e Update oauth.py 2022-08-04 09:33:13 -04:00
Jay Lee
16e85d6d5c pass CA file to fetch_token so it's used by requests 2022-08-03 11:26:36 -04:00
Ross Scroggs
453e65ec53 Clean up calendar ACL documentation (#1545) 2022-08-02 10:05:03 -04:00
Jay Lee
4cbcb9418c Update build.yml 2022-08-02 07:11:48 -04:00
Jay Lee
f2120229e2 Update userinvitations.py 2022-07-20 18:52:32 -04:00
Ross Scroggs
4d2db30000 Update course field names (#1542) 2022-07-18 17:17:11 -04:00
Ross Scroggs
ca575b267b Bound sleep time in create project (#1541) 2022-07-15 13:35:53 -04:00
Jay Lee
3216666a94 retry project creation status check 10 times instead of 5 2022-07-15 11:08:36 -04:00
Ross Scroggs
4ef5606f05 Add cros_ou and cros_ou_and_children to <CrOSTypeEntity> (#1538)
* Add cros_ou and cros_ou_and_children to <CrOSTypeEntity>

Most useful here:
`gam update org|ou <OrgUnitPath> add|move <CrOSTypeEntity>`

* Update GamCommands.txt

* Code cleanup/appease pylint
2022-07-14 13:48:02 -04:00
Ross Scroggs
6122dc3353 Allow print of cros OU and children (#1537) 2022-07-14 09:53:43 -04:00
Jay Lee
14ae792091 Update build.yml 2022-07-13 14:17:14 -04:00
Ross Scroggs
9da5065700 Two updates (#1536)
New CRoS actions

Allow child privileges in create|update adminrole
2022-07-12 14:07:04 -04:00
Jay Lee
22e155998d Update gam-install.sh 2022-06-30 14:56:51 -04:00
Jay Lee
939a1afbbf fix device user printing 2022-06-30 14:48:44 +00:00
Jay Lee
ecda9fe232 fix deviceuser list pageSize 2022-06-30 00:28:10 +00:00
Jay Lee
76eb79eb2d Merge branch 'main' of https://github.com/GAM-team/GAM 2022-06-30 00:12:51 +00:00
Jay Lee
325e597772 Workaround for #1534 2022-06-30 00:12:45 +00:00
Ross Scroggs
c6334d2bcd Allow super admin to delete any shared drive (#1533) 2022-06-27 14:50:17 -04:00
Jay Lee
72ff9ff7e9 rebuild to start using OpenSSL 3.0.4 2022-06-21 12:53:10 -04:00
Jay Lee
b45ad5dcaf Update build.yml 2022-06-17 08:23:29 -04:00
Ross Scroggs
3d14964365 Add address fields to buildings (#1529) 2022-06-10 11:16:15 -04:00
Jay Lee
d15bab5a29 Update build.yml 2022-06-06 13:43:57 -04:00
Jay Lee
c4deb29b21 Update build.yml 2022-05-31 12:20:50 -04:00
Jay Lee
0eafee3105 [no ci] upgrade test runs to Ubuntu 22.04 2022-05-31 11:54:13 -04:00
Ross Scroggs
4d116e81c8 Eliminate orgUnitPath encoding (#1527) 2022-05-31 11:24:37 -04:00
Ross Scroggs
3a41b31e19 Use i, not 6 (#1526) 2022-05-27 16:44:43 -04:00
Jay Lee
f9786468db Merge branch 'main' of https://github.com/GAM-team/GAM 2022-05-27 15:03:47 +00:00
Jay Lee
a49124d1d0 fix org_unit admin role assignment 2022-05-27 14:59:17 +00:00
Ross Scroggs
3087b4b776 Update pip version (#1525) 2022-05-26 09:58:22 -04:00
Jay Lee
aa595fe623 Update var.py 2022-05-26 08:54:27 -04:00
Jay Lee
30a571f56c Update build.yml 2022-05-25 21:39:15 -04:00
Jay Lee
7b0a8dce1e Update build.yml 2022-05-25 20:32:17 -04:00
Jay Lee
6fce941640 Update build.yml 2022-05-25 19:51:13 -04:00
Jay Lee
dbfbf61ddc Update build.yml 2022-05-25 19:18:51 -04:00
Jay Lee
5d618de296 Update build.yml 2022-05-25 17:01:00 -04:00
Ross Scroggs
3e556425ec New Oauth2 error message (#1524) 2022-05-25 16:29:18 -04:00
Jay Lee
370db726a4 Update build.yml 2022-05-21 16:46:28 -04:00
Jay Lee
12d6c8c3f5 Update build.yml 2022-05-21 16:10:52 -04:00
Jay Lee
0fdb6544cc Update build.yml 2022-05-21 16:04:52 -04:00
Jay Lee
ca52845c65 Update build.yml 2022-05-21 15:28:43 -04:00
Jay Lee
fac32f95d8 Update build.yml 2022-05-21 14:40:46 -04:00
Jay Lee
f27b37d21a Update build.yml 2022-05-21 09:09:21 -04:00
Jay Lee
e066fcc172 Make all step if conditions OS generic 2022-05-21 08:39:58 -04:00
Jay Lee
14535e814f [no ci] replace some OS context with generic OS 2022-05-21 08:14:56 -04:00
Jay Lee
1cfb873122 [no ci] undo commented out lines while troubleshooting 2022-05-21 07:47:22 -04:00
Jay Lee
de16285bf2 Update build.yml 2022-05-21 07:21:36 -04:00
Jay Lee
d01f3f31ff Update build.yml 2022-05-21 07:19:12 -04:00
Jay Lee
edec98579d Update build.yml 2022-05-20 22:10:00 -04:00
Jay Lee
ea583e98b2 Update build.yml 2022-05-20 21:39:04 -04:00
Jay Lee
c4a080e081 [no ci] document setuptools_scm issue 2022-05-20 21:31:32 -04:00
Jay Lee
4e35845ecf Update build.yml 2022-05-20 21:05:18 -04:00
Jay Lee
9d76fcbcf1 Update requirements.txt 2022-05-20 09:42:32 -04:00
Jay Lee
60290473e1 Update build.yml 2022-05-20 09:14:17 -04:00
Jay Lee
f94456ce7f Update setup.cfg 2022-05-19 19:16:53 -04:00
Jay Lee
65cda68f40 Update requirements.txt 2022-05-18 03:25:47 -04:00
Jay Lee
576731c386 Update build.yml 2022-05-17 21:22:29 -04:00
Jay Lee
0b56af76b7 Update build.yml 2022-05-17 20:46:23 -04:00
Ross Scroggs
10e6cbabcf Support displaying Chromebook OU ID (#1523) 2022-05-10 14:10:59 -04:00
Jay Lee
f33529a1d4 Update build.yml 2022-05-10 14:10:31 -04:00
Jay Lee
7a5a6a8db2 Update build.yml 2022-05-10 09:50:31 -04:00
Jay Lee
c43c4a8b07 Update build.yml 2022-05-10 08:08:13 -04:00
Jay Lee
0e0e1332c7 Update build.yml 2022-05-10 08:00:33 -04:00
Ross Scroggs
ee19c5e25f Multivalued schema files default to type work; update URLs (#1522)
* multivalued schema field defaults to type work;

* Update URLs

* Add new Product/SKU

* Revert to https://jaylee.us/gam
2022-05-08 19:42:39 -04:00
Ross Scroggs
c0c1355216 Handle missing type field in multivalued schema valus (#1520) 2022-05-04 12:41:35 -04:00
Jay Lee
da3394f3fd Update build.yml 2022-05-03 21:37:27 -04:00
Jay Lee
c856723945 Update var.py 2022-04-29 06:12:36 -04:00
Jay Lee
5e10ccdbdd Update __main__.py 2022-04-29 06:12:00 -04:00
Jay Lee
a49f645647 Update za-bug-report.md 2022-04-29 06:10:51 -04:00
Jay Lee
cf8d714ba7 Update ISSUE_TEMPLATE.txt 2022-04-29 06:10:13 -04:00
Jay Lee
17bb625d0d goodbye git.io :-/ 2022-04-29 06:08:51 -04:00
Ross Scroggs
5e7a858d55 Document delete teamdrive allowitemdeletion (#1517) 2022-04-24 08:00:06 -04:00
Jay Lee
8276474314 GAM 6.21 2022-04-22 18:16:17 -04:00
Jay Lee
d108261654 [test] force shared drive deletion 2022-04-22 19:15:20 +00:00
Jay Lee
aa98be443c create drive file on shared drive 2022-04-22 18:53:02 +00:00
Jay Lee
eecb583f10 Option to force shared drive deletion 2022-04-22 18:48:21 +00:00
Ross Scroggs
809aae27b1 Add option to update cigroup to allow updating a dynamic group to a security group (#1515) 2022-04-17 19:51:15 -04:00
Jay Lee
028ca15498 Create codeql-analysis.yml 2022-04-11 21:45:44 -04:00
Jay Lee
be4403331f Update var.py 2022-04-11 08:44:08 -04:00
Ross Scroggs
b84025debf Use new CI orgUnits.memberships/move (#1514)
Old method never seemed to work; commented code can probably be dropped
2022-04-11 02:33:45 -04:00
Jay Lee
4685f29aba Handle Cloud identity OrgUnits update 500 errors 2022-04-10 19:50:06 -04:00
Ross Scroggs
a832698366 Update CAA documentation (#1513) 2022-04-10 10:41:23 -04:00
Ross Scroggs
cf894fd0bd Code cleanup (#1512)
* Code cleanup

* Two fixes
2022-04-10 08:16:10 -04:00
Ross Scroggs
fd6c04bd94 Fix indentaton (#1511) 2022-04-09 14:20:22 -04:00
Ross Scroggs
49a2352d6d Standardize getting Shared Drive ID or Name (#1510) 2022-04-09 07:26:46 -04:00
Ross Scroggs
291871ec45 Update CAA osType in documentation (#1509) 2022-04-08 22:45:19 -04:00
Jay Lee
73c21a1156 Update caa.py 2022-04-08 19:42:20 -04:00
Ross Scroggs
27eed06617 Document CAA, minimumVersion is optional in constraint (#1508) 2022-04-08 19:39:29 -04:00
Ross Scroggs
a440cbbbdc Make print|show oushareddrives consistent with other commands (#1507) 2022-04-08 15:13:04 -04:00
Jay Lee
eb9ca5eb1d Update build.yml 2022-04-08 15:11:15 -04:00
Ross Scroggs
5eb1277691 Do print|show for caalevels and oushareddrives (#1506) 2022-04-08 12:28:11 -04:00
Ross Scroggs
6de424b185 Two updates (#1504) 2022-04-08 10:43:47 -04:00
Jay Lee
f89491d801 Update build.yml 2022-04-08 10:18:36 -04:00
Jay Lee
154de4818e Update build.yml 2022-04-08 09:57:02 -04:00
Jay Lee
c5895a3082 Update build.yml 2022-04-08 09:40:58 -04:00
Jay Lee
e085257a51 Update var.py 2022-04-08 09:37:22 -04:00
Ross Scroggs
d8e84cf045 Delete extraneous code, custom <String> is operative (#1503) 2022-04-07 21:12:37 -04:00
Jay Lee
a5eb61421d Update build.yml 2022-04-07 20:23:22 -04:00
Jay Lee
cddbea2718 don't use dasa for printer cmds 2022-04-08 00:10:38 +00:00
Jay Lee
c9bf5158e4 capture shared drive id 2022-04-07 23:58:32 +00:00
Jay Lee
7822b36f97 enable commands now that scopes should be present 2022-04-07 23:31:41 +00:00
Jay Lee
20d541ca8e juggle command order to give time for propogation 2022-04-07 23:26:50 +00:00
Jay Lee
72af9fb4a9 Update creds.tar.gpg 2022-04-07 23:17:24 +00:00
Jay Lee
a2972a3329 disable shared drive OU update for now, need to Actions 2022-04-07 20:31:22 +00:00
Jay Lee
97b74c0c8f fix shareddrive name 2022-04-07 20:20:02 +00:00
Jay Lee
332519e5d4 fix gam_user 2022-04-07 20:06:54 +00:00
Jay Lee
881641e2b4 gapi_drive 2022-04-07 19:53:11 +00:00
Jay Lee
82bfe74175 orgunits.py 2022-04-07 19:51:15 +00:00
Jay Lee
bac3451c21 OrgUnit Support for Shared Drives 2022-04-07 19:48:23 +00:00
Ross Scroggs
c97495ab05 Fix bug (#1502) 2022-04-07 15:36:56 -04:00
Jay Lee
aa3dad1e07 Merge branch 'main' of https://github.com/GAM-team/GAM 2022-04-07 17:00:05 +00:00
Jay Lee
2d4e15504c GAM 6.19 2022-04-07 16:59:58 +00:00
Jay Lee
3cd41e3d0f Update build.yml 2022-04-07 11:10:03 -04:00
Jay Lee
a94518c48d Update build.yml 2022-04-07 09:39:18 -04:00
Jay Lee
2837671ed7 Merge branch 'main' of https://github.com/GAM-team/GAM 2022-04-07 12:52:34 +00:00
Jay Lee
e49eed2a24 Improve error instructions for CAA 2022-04-07 12:52:21 +00:00
Jay Lee
edfc27c960 Update project-apis.txt 2022-04-07 08:29:25 -04:00
Jay Lee
41a10932cb handle >1 access policies in org, action tests 2022-04-07 01:21:15 +00:00
Jay Lee
119538c10c Support for CAA levels 2022-04-07 00:48:40 +00:00
Ross Scroggs
0a03fbb82e Fix add license message (#1497) 2022-03-31 13:38:18 -04:00
Ross Scroggs
b838054e2f Update pip install version (#1496) 2022-03-30 11:59:23 -04:00
Jay Lee
45ac118381 GAM 6.18 2022-03-30 08:16:20 -04:00
Jay Lee
e1600eadbc remove noupx and onefile args that already exist in spec file 2022-03-30 07:29:11 -04:00
Jay Lee
cc23f98078 [no cd] strip non-Win builds 2022-03-30 07:27:31 -04:00
Jay Lee
b68cc671eb Update build.yml 2022-03-29 20:10:44 -04:00
Jay Lee
8c215a0a0b Update build.yml 2022-03-29 20:10:21 -04:00
Jay Lee
374c6a9367 Update gam-install.sh 2022-03-29 19:53:04 -04:00
Jay Lee
5d93d9893e Update build.yml 2022-03-29 16:04:23 -04:00
Jay Lee
010c26ea89 Update build.yml 2022-03-29 14:23:28 -04:00
Jay Lee
5da90f7585 Update build.yml 2022-03-29 14:22:43 -04:00
Jay Lee
5356591d9c Update build.yml 2022-03-28 20:18:01 -04:00
Jay Lee
8df5d22805 Update build.yml 2022-03-28 19:39:17 -04:00
Jay Lee
5684ab3c05 Update build.yml 2022-03-28 18:33:38 -04:00
Jay Lee
d7b8f4c228 Update build.yml 2022-03-28 18:29:41 -04:00
Jay Lee
66fe03bbcd Update build.yml 2022-03-28 16:09:11 -04:00
Ross Scroggs
e7496dc9cb Fix bug in print vaultcounts ... account emailaddress (#1493) 2022-03-26 16:42:19 -04:00
Jay Lee
3b52af0b8a Update build.yml 2022-03-25 19:57:09 -04:00
Jay Lee
4ffe709ab9 Update build.yml 2022-03-16 12:28:35 -04:00
Jay Lee
c0c15a3ee5 Update build.yml 2022-03-16 08:33:51 -04:00
Jay Lee
b724330cb1 Update var.py 2022-03-14 15:49:11 -04:00
Jay Lee
8b9ce17959 Update gam-install.sh 2022-03-08 14:26:59 -05:00
Jay Lee
26116474c5 Update build.yml 2022-03-08 07:54:50 -05:00
Jay Lee
3411fd8557 Update build.yml 2022-03-08 07:22:01 -05:00
Jay Lee
0bee3e38a0 Update build.yml 2022-03-07 16:00:58 -05:00
Jay Lee
dcc2224657 Update build.yml 2022-03-07 15:24:07 -05:00
Jay Lee
1bf1c43f23 GAM 6.17 2022-03-07 20:11:56 +00:00
Jay Lee
53b336aee9 Try universal ToS. Fixes #1489 2022-03-07 19:51:43 +00:00
Jay Lee
94b5407cb4 Update build.yml 2022-03-04 14:52:59 -05:00
Jay Lee
e7357e69fb Update build.yml 2022-03-04 10:05:51 -05:00
Jay Lee
d5a036dfc6 Update build.yml 2022-03-04 08:23:32 -05:00
Jay Lee
e256de06d9 Update build.yml 2022-03-04 06:05:03 -05:00
Jay Lee
59beba616c Update build.yml 2022-03-03 21:53:50 -05:00
Jay Lee
735747e0d4 Update build.yml 2022-03-03 21:02:10 -05:00
Jay Lee
ab167d8c4c Update build.yml 2022-03-03 20:59:58 -05:00
Jay Lee
944f010a8c Update build.yml 2022-03-03 20:00:40 -05:00
Jay Lee
dd064a3843 Update build.yml 2022-03-03 18:14:20 -05:00
Jay Lee
244b20e1f0 Update build.yml 2022-03-03 18:12:40 -05:00
Jay Lee
88308a352b Update build.yml 2022-03-03 18:07:40 -05:00
Jay Lee
c63df9d245 Update build.yml 2022-03-03 17:20:11 -05:00
Jay Lee
2780ad2edc Update build.yml 2022-03-03 17:15:20 -05:00
Jay Lee
00b3122c2c Update build.yml 2022-03-03 14:13:05 -05:00
Jay Lee
18221be556 Update build.yml 2022-03-03 13:14:04 -05:00
Jay Lee
80abd9284f Update build.yml 2022-03-03 12:10:27 -05:00
Jay Lee
87b6cb073f Update build.yml 2022-03-03 12:03:55 -05:00
Jay Lee
e2cbbb2c93 Update build.yml 2022-03-03 09:30:08 -05:00
Jay Lee
c771b84463 Update build.yml 2022-03-02 20:40:31 -05:00
Jay Lee
2460e6957f Update build.yml 2022-03-02 18:35:55 -05:00
Jay Lee
0ec42eb796 Update build.yml 2022-03-02 10:40:37 -05:00
Jay Lee
b78b5ea9e1 Update build.yml 2022-03-02 10:31:27 -05:00
Jay Lee
d26bfc9aab Update build.yml 2022-03-02 09:21:52 -05:00
Jay Lee
c64730e07b Update build.yml 2022-03-02 09:19:45 -05:00
Jay Lee
f8b00b92b4 Update build.yml 2022-03-02 08:21:24 -05:00
Jay Lee
3c9ec2578e Update build.yml 2022-03-01 21:44:43 -05:00
Jay Lee
5f79f46e30 Update build.yml 2022-03-01 20:41:32 -05:00
Jay Lee
30c250c314 Update build.yml 2022-03-01 20:29:44 -05:00
Jay Lee
69f504d91c Update build.yml 2022-03-01 20:02:45 -05:00
Jay Lee
c505dd9c2b Update build.yml 2022-03-01 19:59:53 -05:00
Jay Lee
a7638dee0a Update build.yml 2022-03-01 17:14:31 -05:00
Jay Lee
b8e5ad5107 Update build.yml 2022-03-01 14:09:39 -05:00
Jay Lee
0b2d04bc6f Update build.yml 2022-03-01 13:22:39 -05:00
Jay Lee
803025b8c5 Update build.yml 2022-03-01 13:17:42 -05:00
Jay Lee
a7154da0b6 Update build.yml 2022-03-01 13:15:19 -05:00
Jay Lee
d04b33d1d9 [no ci] don't install Linux packages on cached runs 2022-03-01 12:26:22 -05:00
Jay Lee
614edc22ca Update gam.spec 2022-03-01 12:20:32 -05:00
Jay Lee
eeb760260a Update build.yml 2022-03-01 11:32:32 -05:00
Jay Lee
eb5d876487 Update build.yml 2022-03-01 10:45:50 -05:00
Jay Lee
3bf2c5c8b6 Update build.yml 2022-03-01 10:07:26 -05:00
Jay Lee
3c24049d66 Update build.yml 2022-03-01 09:27:42 -05:00
Jay Lee
1f1b6d45e3 Update build.yml 2022-03-01 09:00:00 -05:00
Jay Lee
7b8a17a544 Update build.yml 2022-03-01 08:35:41 -05:00
Jay Lee
95d1b1295e Update build.yml 2022-03-01 08:20:09 -05:00
Jay Lee
4abd0407c8 Update build.yml 2022-03-01 08:13:59 -05:00
Jay Lee
65f229875d Update build.yml 2022-03-01 08:11:53 -05:00
Jay Lee
9be84501ec Update build.yml 2022-03-01 07:20:23 -05:00
Jay Lee
6e400cabd0 Update build.yml 2022-02-28 22:11:00 -05:00
Jay Lee
1f00614551 Update build.yml 2022-02-28 21:45:59 -05:00
Jay Lee
1da61d076e Update build.yml 2022-02-28 21:26:08 -05:00
Jay Lee
2ce04b4dd2 Update build.yml 2022-02-28 21:10:38 -05:00
Jay Lee
94a52c80cd Update build.yml 2022-02-28 20:49:17 -05:00
Jay Lee
af71cf9a82 Update build.yml 2022-02-28 20:41:50 -05:00
Jay Lee
594c7d6d29 Update build.yml 2022-02-28 20:30:54 -05:00
Jay Lee
4575c3576f Update build.yml 2022-02-28 20:26:22 -05:00
Jay Lee
243a6d20cc Update build.yml 2022-02-28 20:19:57 -05:00
Jay Lee
9f27cc155a Update build.yml 2022-02-28 20:16:54 -05:00
Jay Lee
64bfa122bd Update build.yml 2022-02-28 20:04:31 -05:00
Jay Lee
5be9a3f219 Update build.yml 2022-02-28 20:01:50 -05:00
Jay Lee
b0a478c156 Update build.yml 2022-02-28 19:58:23 -05:00
Jay Lee
96b6a0bc2c Update build.yml 2022-02-28 19:44:01 -05:00
Jay Lee
c3b79c5330 Update build.yml 2022-02-28 16:43:32 -05:00
Jay Lee
da54738902 Update build.yml 2022-02-28 16:40:02 -05:00
Jay Lee
ced508443a Update build.yml 2022-02-28 16:38:20 -05:00
Jay Lee
22d0446da4 Update build.yml 2022-02-28 16:34:01 -05:00
Jay Lee
8c7b3455c9 Update build.yml 2022-02-28 16:30:58 -05:00
Jay Lee
eac145f010 Update build.yml 2022-02-28 16:20:30 -05:00
Jay Lee
8765d06c2f Update build.yml 2022-02-28 16:17:54 -05:00
Jay Lee
7d19450da7 Update build.yml 2022-02-28 16:14:26 -05:00
Jay Lee
4edaeee883 Update build.yml 2022-02-28 16:12:51 -05:00
Jay Lee
e44ea5dbed Apple M1 support part 1 2022-02-28 16:07:17 -05:00
Jay Lee
7ae61b0c6d Update build.yml 2022-02-28 14:30:45 -05:00
Jay Lee
f0ffdc371f Update build.yml 2022-02-28 14:29:02 -05:00
34 changed files with 1357 additions and 344 deletions

View File

@@ -1,8 +1,8 @@
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
* I have upgraded to the latest GAM release from https://github.com/GAM-team/GAM/releases and I still have this issue.
* I am typing the command as described in the GAM Wiki at https://github.com/GAM-team/GAM/wiki
Full steps to reproduce the issue:
1.

View File

@@ -10,7 +10,7 @@ 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 have upgraded to the latest GAM release from https://github.com/GAM-team/GAM/releases 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:

Binary file not shown.

View File

@@ -12,10 +12,10 @@ defaults:
working-directory: src
env:
OPENSSL_CONFIG_OPTS: no-asm no-fips
OPENSSL_INSTALL_PATH: ${{ github.workspace }}/src/ssl
OPENSSL_CONFIG_OPTS: no-fips
OPENSSL_INSTALL_PATH: ${{ github.workspace }}/bin/ssl
OPENSSL_SOURCE_PATH: ${{ github.workspace }}/src/openssl
PYTHON_INSTALL_PATH: ${{ github.workspace }}/src/python
PYTHON_INSTALL_PATH: ${{ github.workspace }}/bin/python
PYTHON_SOURCE_PATH: ${{ github.workspace }}/src/cpython
jobs:
@@ -24,48 +24,53 @@ jobs:
strategy:
matrix:
include:
- os: ubuntu-20.04
- os: ubuntu-22.04
jid: 1
goal: build
arch: x86_64
openssl_archs: linux-x86_64
- os: [self-hosted, linux, arm64]
jid: 2
goal: build
arch: x86_64
- os: macos-11
jid: 3
goal: build
arch: x86_64
- os: windows-2022
arch: aarch64
openssl_archs: linux-aarch64
- os: macos-12
jid: 4
goal: build
arch: Win64
arch: universal2
openssl_archs: darwin64-x86_64 darwin64-arm64
- os: windows-2022
jid: 5
goal: build
arch: Win64
openssl_archs: VC-WIN64A
- os: windows-2022
jid: 6
goal: build
arch: Win32
- os: ubuntu-20.04
openssl_archs: VC-WIN32
- os: ubuntu-22.04
goal: test
python: "3.7"
jid: 6
arch: x86_64
- os: ubuntu-20.04
goal: test
python: "3.8"
jid: 7
arch: x86_64
- os: ubuntu-20.04
- os: ubuntu-22.04
goal: test
python: "3.9"
python: "3.8"
jid: 8
arch: x86_64
- os: [self-hosted, linux, arm64]
- os: ubuntu-22.04
goal: test
python: "3.9"
jid: 9
goal: build
arch: aarch64
- os: [self-hosted, linux, arm]
arch: x86_64
- os: ubuntu-22.04
goal: test
python: "3.11-dev"
jid: 10
goal: build
arch: armv7l
arch: x86_64
steps:
- uses: actions/checkout@master
with:
persist-credentials: false
@@ -76,9 +81,8 @@ jobs:
id: cache-python-ssl
with:
path: |
src/python
src/ssl
key: gam-${{ matrix.jid }}-20220131
bin
key: gam-${{ matrix.jid }}-20220802
- name: Use pre-compiled Python for testing
if: matrix.python != ''
@@ -105,18 +109,33 @@ jobs:
echo "JID=${JID}" >> $GITHUB_ENV
echo "ACTIONS_CACHE=${ACTIONS_CACHE}" >> $GITHUB_ENV
echo "ACTIONS_GOAL=${ACTIONS_GOAL}" >> $GITHUB_ENV
echo "date=date" >> $GITHUB_ENV
- name: Install necessary hosted Linux packages
if: matrix.os == 'ubuntu-20.04'
- name: Install necessary Github-hosted Linux packages
if: runner.os == 'Linux' && runner.arch == 'X64'
run: |
echo "RUNNING: apt update..."
sudo apt-get -qq --yes update
sudo apt-get -qq --yes install swig libpcsclite-dev
- name: MacOS remove Homebrew
if: runner.os == 'macOS'
run: |
# remove everything except the libraries needed by yubikey-manager
brew uninstall $(brew list | grep -v 'pcre\|swig\|pcsc-lite')
- name: MacOS install tools
if: runner.os == 'macOS'
run: |
# Install latest Rust
curl -fsS -o rust.sh https://sh.rustup.rs
bash ./rust.sh -y
source $HOME/.cargo/env
# needed for Rust to compile cryptography Python package for universal2
rustup target add aarch64-apple-darwin
- name: Windows Configure VCode
uses: ilammy/msvc-dev-cmd@v1
if: matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
with:
arch: ${{ matrix.arch }}
@@ -125,54 +144,44 @@ jobs:
env:
arch: ${{ matrix.arch }}
jid: ${{ matrix.jid }}
openssl_archs: ${{ matrix.openssl_archs }}
run: |
echo "We are running on ${RUNNER_OS}"
if [[ "${arch}" == "Win64" ]]; then
PYEXTERNALS_PATH="amd64"
PYBUILDRELEASE_ARCH="x64"
OPENSSL_CONFIG_TARGET="VC-WIN64A"
GAM_ARCHIVE_ARCH="x86_64"
WIX_ARCH="x64"
CHOC_OPS=""
elif [[ "${arch}" == "Win32" ]]; then
PYEXTERNALS_PATH="win32"
PYBUILDRELEASE_ARCH="Win32"
OPENSSL_CONFIG_TARGET="VC-WIN32"
GAM_ARCHIVE_ARCH="x86"
WIX_ARCH="x86"
CHOC_OPS="--forcex86"
fi
if [[ "${RUNNER_OS}" == "macOS" ]]; then
brew install coreutils
brew install bash
#brew install coreutils
#brew install bash
MAKE=make
MAKEOPT="-j$(sysctl -n hw.logicalcpu)"
PERL=perl
# We only care about non-deprecated OSes
MACOSX_DEPLOYMENT_TARGET="10.15"
echo "MACOSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET}" >> $GITHUB_ENV
echo "MACOSX_DEPLOYMENT_TARGET=10.15" >> $GITHUB_ENV
echo "PYTHON=${PYTHON_INSTALL_PATH}/bin/python3" >> $GITHUB_ENV
export date=gdate
export realpath=grealpath
echo "PIP_ARGS=--no-binary=:all:" >> $GITHUB_ENV
elif [[ "${RUNNER_OS}" == "Linux" ]]; then
MAKE=make
MAKEOPT="-j$(nproc)"
PERL=perl
echo "PYTHON=${PYTHON_INSTALL_PATH}/bin/python3" >> $GITHUB_ENV
export date=date
export realpath=realpath
elif [[ "${RUNNER_OS}" == "Windows" ]]; then
MAKE=nmake
MAKEOPT=""
PERL="c:\strawberry\perl\bin\perl.exe"
echo "PYTHON=${PYTHON_INSTALL_PATH}\python.exe" >> $GITHUB_ENV
export date=date
export realpath=realpath
echo "GAM_ARCHIVE_ARCH=${GAM_ARCHIVE_ARCH}" >> $GITHUB_ENV
echo "WIX_ARCH=${WIX_ARCH}" >> $GITHUB_ENV
fi
echo "date=${date}" >> $GITHUB_ENV
echo "realpath=${realpath}" >> $GITHUB_ENV
echo "We'll run make with: ${MAKEOPT}"
echo "JID=${jid}" >> $GITHUB_ENV
echo "arch=${arch}" >> $GITHUB_ENV
@@ -181,13 +190,15 @@ jobs:
echo "PERL=${PERL}" >> $GITHUB_ENV
echo "PYEXTERNALS_PATH=${PYEXTERNALS_PATH}" >> $GITHUB_ENV
echo "PYBUILDRELEASE_ARCH=${PYBUILDRELEASE_ARCH}" >> $GITHUB_ENV
echo "OPENSSL_CONFIG_TARGET=${OPENSSL_CONFIG_TARGET}" >> $GITHUB_ENV
echo "openssl_archs=${openssl_archs}" >> $GITHUB_ENV
echo "LD_LIBRARY_PATH=${OPENSSL_INSTALL_PATH}/lib:${PYTHON_INSTALL_PATH}/lib" >> $GITHUB_ENV
#echo "PATH=${PATH}:${PYTHON_INSTALL_PATH}/scripts" >> $GITHUB_ENV
- name: Get latest stable OpenSSL source
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
mkdir -vp "${GITHUB_WORKSPACE}/src"
cd "${GITHUB_WORKSPACE}/src"
git clone https://github.com/openssl/openssl.git
cd "${OPENSSL_SOURCE_PATH}"
export LATEST_STABLE_TAG=$(git tag --list openssl-* | grep -v alpha | grep -v beta | sort -Vr | head -n1)
@@ -195,44 +206,86 @@ jobs:
git checkout "${LATEST_STABLE_TAG}"
export COMPILED_OPENSSL_VERSION=${LATEST_STABLE_TAG:8} # Trim the openssl- prefix
echo "COMPILED_OPENSSL_VERSION=${COMPILED_OPENSSL_VERSION}" >> $GITHUB_ENV
if [[ "${RUNNER_OS}" == "macOS" ]]; then
for openssl_arch in $openssl_archs; do
ssldir="${OPENSSL_SOURCE_PATH}-${openssl_arch}"
mkdir -v "${ssldir}"
cp -vrf ${OPENSSL_SOURCE_PATH}/* "${ssldir}/"
done
rm -vrf "${OPENSSL_SOURCE_PATH}"
else
mv -v "${OPENSSL_SOURCE_PATH}" "${OPENSSL_SOURCE_PATH}-${openssl_archs}"
fi
- name: Windows NASM Install
uses: ilammy/setup-nasm@v1
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
- name: Config OpenSSL
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
cd "${OPENSSL_SOURCE_PATH}"
# --libdir=lib is needed so Python can find OpenSSL libraries
"${PERL}" ./Configure "${OPENSSL_CONFIG_TARGET}" --libdir=lib --prefix="${OPENSSL_INSTALL_PATH}" $OPENSSL_CONFIG_OPTS
for openssl_arch in $openssl_archs; do
cd "${GITHUB_WORKSPACE}/src/openssl-${openssl_arch}"
# --libdir=lib is needed so Python can find OpenSSL libraries
"${PERL}" ./Configure "${openssl_arch}" --libdir=lib --prefix="${OPENSSL_INSTALL_PATH}" $OPENSSL_CONFIG_OPTS
done
- name: Rename GNU link on Windows
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: bash
run: mv /usr/bin/link /usr/bin/gnulink
- name: Make OpenSSL
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
cd "${OPENSSL_SOURCE_PATH}"
$MAKE "${MAKEOPT}"
for openssl_arch in $openssl_archs; do
cd "${GITHUB_WORKSPACE}/src/openssl-${openssl_arch}"
$MAKE "${MAKEOPT}"
done
- name: Install OpenSSL
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
cd "${OPENSSL_SOURCE_PATH}"
# install_sw saves us ages processing man pages :-)
$MAKE install_sw
if [[ "${RUNNER_OS}" == "macOS" ]]; then
for openssl_arch in $openssl_archs; do
cd "${GITHUB_WORKSPACE}/src/openssl-${openssl_arch}"
# install_sw saves us ages processing man pages :-)
$MAKE install_sw
mv "${OPENSSL_INSTALL_PATH}" "${GITHUB_WORKSPACE}/bin/ssl-${openssl_arch}"
done
mkdir -vp "${OPENSSL_INSTALL_PATH}/lib"
mkdir -vp "${OPENSSL_INSTALL_PATH}/bin"
for archlib in libcrypto.3.dylib libssl.3.dylib libcrypto.a libssl.a; do
lipo -create "${GITHUB_WORKSPACE}/bin/ssl-darwin64-x86_64/lib/${archlib}" \
"${GITHUB_WORKSPACE}/bin/ssl-darwin64-arm64/lib/${archlib}" \
-output "${GITHUB_WORKSPACE}/bin/ssl/lib/${archlib}"
done
mv ${GITHUB_WORKSPACE}/bin/ssl-darwin64-x86_64/include ${GITHUB_WORKSPACE}/bin/ssl/
lipo -create "${GITHUB_WORKSPACE}/bin/ssl-darwin64-x86_64/bin/openssl" \
"${GITHUB_WORKSPACE}/bin/ssl-darwin64-arm64/bin/openssl" \
-output "${GITHUB_WORKSPACE}/bin/ssl/bin/openssl"
rm -rf ${GITHUB_WORKSPACE}/bin/ssl-darwin64-x86_64
rm -rf ${GITHUB_WORKSPACE}/bin/ssl-darwin64-arm64
echo "LDFLAGS=-L${OPENSSL_INSTALL_PATH}/lib" >> $GITHUB_ENV
echo "CRYPTOGRAPHY_SUPPRESS_LINK_FLAGS=1" >> $GITHUB_ENV
echo "CFLAGS=-I${OPENSSL_INSTALL_PATH}/include -arch arm64 -arch x86_64" >> $GITHUB_ENV
echo "ARCHFLAGS=-arch x86_64 -arch arm64" >> $GITHUB_ENV
else
cd "${GITHUB_WORKSPACE}/src/openssl-${openssl_archs}"
# install_sw saves us ages processing man pages :-)
$MAKE install_sw
fi
- name: Run OpenSSL
if: matrix.goal == 'build'
run: |
"${OPENSSL_INSTALL_PATH}/bin/openssl" version
file "${OPENSSL_INSTALL_PATH}/bin/openssl"
- name: Get latest stable Python source
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
cd "${GITHUB_WORKSPACE}/src"
git clone https://github.com/python/cpython.git
cd "${PYTHON_SOURCE_PATH}"
export LATEST_STABLE_TAG=$(git tag --list | grep -v a | grep -v rc | grep -v b | sort -Vr | head -n1)
@@ -241,25 +294,31 @@ jobs:
echo "COMPILED_PYTHON_VERSION=${COMPILED_PYTHON_VERSION}" >> $GITHUB_ENV
- name: Mac/Linux Configure Python
if: matrix.goal == 'build' && matrix.os != 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os != 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
cd "${PYTHON_SOURCE_PATH}"
if [[ "${RUNNER_OS}" == "macOS" ]]; then
extra_args=( "--enable-universalsdk" "--with-universal-archs=universal2" )
else
extra_args=( )
fi
./configure --with-openssl="${OPENSSL_INSTALL_PATH}" \
--prefix="${PYTHON_INSTALL_PATH}" \
--enable-shared \
--with-ensurepip=upgrade \
--enable-optimizations \
--with-lto
--with-lto \
"${extra_args[@]}"
- name: Windows Get External Python deps
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
cd "${env:PYTHON_SOURCE_PATH}"
PCBuild\get_externals.bat
- name: Windows overwrite external OpenSSL with local
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
cd "${env:PYTHON_SOURCE_PATH}"
@@ -271,14 +330,14 @@ jobs:
$env:OPENSSL_EXT_TARGET_PATH = "${env:OPENSSL_EXT_PATH}${env:PYEXTERNALS_PATH}"
echo "Copying our OpenSSL to ${env:OPENSSL_EXT_TARGET_PATH}"
mkdir "${env:OPENSSL_EXT_TARGET_PATH}\include\openssl\"
Copy-Item -Path "${env:OPENSSL_SOURCE_PATH}\LICENSE.txt" -Destination "${env:OPENSSL_EXT_TARGET_PATH}\LICENSE"
Copy-Item -Path "${env:GITHUB_WORKSPACE}/src/openssl-${env:openssl_archs}\LICENSE.txt" -Destination "${env:OPENSSL_EXT_TARGET_PATH}\LICENSE"
cp -v "$env:OPENSSL_INSTALL_PATH\lib\*" "${env:OPENSSL_EXT_TARGET_PATH}"
cp -v "$env:OPENSSL_INSTALL_PATH\bin\*" "${env:OPENSSL_EXT_TARGET_PATH}"
cp -v "$env:OPENSSL_INSTALL_PATH\include\openssl\*" "${env:OPENSSL_EXT_TARGET_PATH}\include\openssl\"
cp -v "$env:OPENSSL_INSTALL_PATH\include\openssl\applink.c" "${env:OPENSSL_EXT_TARGET_PATH}\include\"
- name: Windows Install sphinx-build
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
pip install --upgrade pip
@@ -286,7 +345,7 @@ jobs:
sphinx-build --version
- name: Windows Config/Build Python
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
cd "${env:PYTHON_SOURCE_PATH}"
@@ -296,7 +355,7 @@ jobs:
PCBuild\build.bat -m --pgo -c Release -p "${env:PYBUILDRELEASE_ARCH}"
- name: Windows Install Python
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
cd "${env:PYTHON_SOURCE_PATH}"
@@ -308,14 +367,14 @@ jobs:
Copy-Item -Path "${env:PYTHON_SOURCE_PATH}\PC\*.h" "${env:PYTHON_INSTALL_PATH}\include\"
- name: Mac/Linux Build Python
if: matrix.goal == 'build' && matrix.os != 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os != 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
cd "${PYTHON_SOURCE_PATH}"
echo "Running: ${MAKE} ${MAKEOPT}"
$MAKE $MAKEOPT
- name: Mac/Linux Install Python
if: matrix.goal == 'build' && matrix.os != 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os != 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
cd "${PYTHON_SOURCE_PATH}"
$MAKE altinstall
@@ -348,13 +407,26 @@ jobs:
fi
echo "PyInstaller build arguments: ${PYINSTALLER_BUILD_ARGS}"
"${PYTHON}" ./waf all $PYINSTALLER_BUILD_ARGS
cd ..
"${PYTHON}" -m pip install .
cd ../..
echo "---- Installing PyInstaller ----"
"${PYTHON}" -m pip install pyinstaller
- name: Install pip requirements
run: |
set +e
"${PYTHON}" -m pip install --upgrade -r requirements.txt
if [[ "${RUNNER_OS}" == "macOS" ]]; then
for package in cryptography; do
"${PYTHON}" -m pip install --upgrade cffi ${PIP_ARGS}
"${PYTHON}" -m pip download --only-binary :all: \
--dest . \
--no-cache \
--no-deps \
--platform macosx_10_15_universal2 \
$package
"${PYTHON}" -m pip install --force-reinstall --no-deps $package*.whl
done
find $PYTHON_INSTALL_PATH/lib/python3.10/site-packages -type f -name "*.so" -exec du -sh "{}" \;
fi
"${PYTHON}" -m pip install --upgrade -r requirements.txt ${PIP_ARGS}
"${PYTHON}" -m pip list
- name: Build GAM with PyInstaller
@@ -362,12 +434,16 @@ jobs:
run: |
export gampath="./dist/gam"
mkdir -p -v "${gampath}"
export gampath="$($realpath $gampath)"
if [[ "${RUNNER_OS}" == "macOS" ]]; then
export gampath=$($PYTHON -c "import os; print(os.path.realpath('$gampath'))")
else
export gampath=$(realpath "${gampath}")
fi
export gam="${gampath}/gam"
echo "gampath=${gampath}" >> $GITHUB_ENV
echo "gam=${gam}" >> $GITHUB_ENV
echo -e "GAM: ${gam}\nGAMPATH: ${gampath}"
"${PYTHON}" -m PyInstaller --clean --noupx --strip --onefile --distpath="${gampath}" gam.spec
"${PYTHON}" -m PyInstaller --clean --distpath="${gampath}" gam.spec
- name: Basic Tests all jobs
run: |
@@ -378,43 +454,52 @@ jobs:
echo "GAMVERSION=${GAMVERSION}" >> $GITHUB_ENV
- name: Linux/MacOS package
if: matrix.os != 'windows-2022' && matrix.goal == 'build'
if: runner.os != 'Windows' && matrix.goal == 'build'
run: |
cp -v LICENSE $gampath
cp -v GamCommands.txt $gampath
if [[ "${RUNNER_OS}" == "macOS" ]]; then
GAM_ARCHIVE="gam-${GAMVERSION}-macos-x86_64.tar.xz"
GAM_ARCHIVE="gam-${GAMVERSION}-macos-universal2.tar.xz"
elif [[ "${RUNNER_OS}" == "Linux" ]]; then
this_glibc_ver=$(ldd --version | awk '/ldd/{print $NF}')
GAM_ARCHIVE="gam-${GAMVERSION}-linux-$(arch)-glibc${this_glibc_ver}.tar.xz"
fi
tar -C dist/ --create --verbose --exclude-from "${GITHUB_WORKSPACE}/.github/actions/package_exclusions.txt" --file $GAM_ARCHIVE --xz gam
- name: Linux install patchelf/staticx
if: matrix.os == 'ubuntu-20.04' && matrix.goal != 'test'
- name: Linux 64-bit install patchelf/staticx
if: runner.os == 'Linux' && contains(runner.arch, '64') && matrix.goal != 'test'
run: |
"${PYTHON}" -m pip install --upgrade patchelf-wrapper
"${PYTHON}" -m pip install --upgrade staticx
- name: Linux Make Static
if: matrix.os == 'ubuntu-20.04' && matrix.goal != 'test'
- name: Linux 64-bit Make Static
if: runner.os == 'Linux' && contains(runner.arch, '64') && matrix.goal != 'test'
run: |
$PYTHON -m staticx "${gam}" "${gam}-staticx"
case $RUNNER_ARCH in
X64)
ldlib=/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
;;
ARM64)
ldlib=/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
;;
esac
echo "ldlib=${ldlib}"
$PYTHON -m staticx -l "${ldlib}" "${gam}" "${gam}-staticx"
- name: Linux Run StaticX-ed
if: matrix.os == 'ubuntu-20.04' && matrix.goal != 'test'
if: runner.os == 'Linux' && contains(runner.arch, '64') && matrix.goal != 'test'
run: |
"${gam}-staticx" version extended
mv -v "${gam}-staticx" "${gam}"
- name: Linux package staticx
if: matrix.os == 'ubuntu-20.04' && matrix.goal != 'test'
if: runner.os == 'Linux' && contains(runner.arch, '64') && matrix.goal != 'test'
run: |
GAM_ARCHIVE="gam-${GAMVERSION}-linux-x86_64-legacy.tar.xz"
GAM_ARCHIVE="gam-${GAMVERSION}-linux-$(uname -m)-legacy.tar.xz"
tar -C dist/ --create --verbose --exclude-from "${GITHUB_WORKSPACE}/.github/actions/package_exclusions.txt" --file $GAM_ARCHIVE --xz gam
- name: Windows package
if: matrix.os == 'windows-2022' && matrix.goal != 'test'
if: runner.os == 'Windows' && matrix.goal != 'test'
run: |
cp -v LICENSE $gampath
cp -v GamCommands.txt $gampath
@@ -452,18 +537,22 @@ jobs:
env:
PASSCODE: ${{ secrets.PASSCODE }}
run: |
if [[ "${RUNNER_OS}" == "macOS" ]]; then
brew install gnupg
fi
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 checkconn
$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 tstamp=$($PYTHON -c "import time; print(time.time_ns())")
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
@@ -471,7 +560,7 @@ jobs:
export newresource=$newbase-resource
export GAM_THREADS=5
echo email > sample.csv;
for i in {01..10}; do
for i in {1..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 languages en+,en-GB-
@@ -481,13 +570,13 @@ jobs:
$gam create group $newgroup name "GHA $JID group" description "This is a description" isarchived true
$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 user $newuser add license workspaceenterpriseplus
$gam print privileges
$gam update cigroup $newgroup memberrestriction 'member.type == 1 || member.customer_id == groupCustomerId()'
$gam info cigroup $newgroup
$gam user $newuser add license workspaceenterpriseplus
$gam update group $newgroup add owner $gam_user
$gam update group $newgroup add member $newuser
$gam print privileges
$gam create admin $newuser _GROUPS_EDITOR_ROLE CUSTOMER condition nonsecuritygroup
$gam create admin $newuser _GROUPS_EDITOR_ROLE CUSTOMER # condition nonsecuritygroup
$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 ""
@@ -502,7 +591,7 @@ jobs:
$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 add contactdelegate "${newbase}-bulkuser-1"
#$gam user $newuser print contactdelegates
export biohazard=$(echo -e '\xe2\x98\xa3')
$gam user $newuser label "$biohazard unicode biohazard $biohazard"
@@ -513,10 +602,10 @@ jobs:
$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 delegate to "${newbase}-bulkuser-1"
$gam users "$gam_user $newbase-bulkuser-1 $newbase-bulkuser-2 $newbase-bulkuser-3" delete messages query in:anywhere maxtodelete 99999 doit
$gam users "$newbase-bulkuser-4 $newbase-bulkuser-5 $newbase-bulkuser-6" trash messages query in:anywhere maxtotrash 99999 doit
$gam users "$newbase-bulkuser-7 $newbase-bulkuser-8 $newbase-bulkuser-9" modify messages query in:anywhere maxtomodify 99999 addlabel IMPORTANT addlabel STARRED doit
$gam user $newuser delete label --ALL_LABELS--
GAM_CSV_ROW_FILTER="name:regex:gha-test-${JID}" $gam print features | $gam csv - gam delete feature ~name
$gam create feature name Whiteboard-$newbase
@@ -531,14 +620,16 @@ jobs:
$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
starttime=$($PYTHON -c "import datetime; print((datetime.datetime.now() + datetime.timedelta(hours=1)).strftime('%Y-%m-%dT%H:%M:%S.%f+00:00'))")
endtime=$($PYTHON -c "import datetime; print((datetime.datetime.now() + datetime.timedelta(hours=2)).strftime('%Y-%m-%dT%H:%M:%S.%f+00:00'))")
$gam calendar $gam_user addevent summary "GHA test event" start "${starttime}" end "${endtime}" 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 use_new_export true
$gam create vaultexport matter $matterid name "GHA export $newbase" corpus mail accounts $newuser use_new_export false
$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
@@ -564,34 +655,44 @@ jobs:
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 orderby serialnumber
#$gam show crostelemetry storagepercentonly
$gam show crostelemetry storagepercentonly
$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
#$gam print userinvitations
#$gam print userinvitations | $gam csv - gam send userinvitation ~name
$gam create caalevel "zzz_${newbase}" basic condition ipsubnetworks 1.1.1.1/32,2.2.2.2/32 endcondition
$gam print caalevels
$gam delete caalevel "zzz_${newbase}"
driveid=$($gam user $gam_user add shareddrive "${newbase}" | awk '{print $NF}')
echo "Created shared drive ${driveid}"
$gam user $gam_user add drivefile localfile gam.py parentid "${driveid}"
$gam user $gam_user update shareddrive "${driveid}" ou "id:03ph8a2z1t2ph5z"
$gam user $gam_user show shareddrives asadmin
$gam user $gam_user delete shareddrive "${driveid}" nukefromorbit
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 -f -v $gampath/enabledasa.txt
#$gam create printer displayname "${newbase}" uri ipp://localhost:631 driverless description "made by $(gam_user)" ou /
#export CUSTOMER_ID="C01wfv983"
#export GA_DOMAIN="pdl.jaylee.us"
#touch $gampath/enabledasa.txt
#echo "using delegated admin service account"
#$gam print users
- 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: 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

70
.github/workflows/codeql-analysis.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ main ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ main ]
schedule:
- cron: '25 10 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://git.io/codeql-language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@@ -9,7 +9,7 @@ GAM is a command line tool for Google Workspace admins to manage domain and user
Open a terminal and run:
```sh
bash <(curl -s -S -L https://git.io/install-gam)
bash <(curl -s -S -L https://gam-shortn.appspot.com/gam-install)
```
this will download GAM, install it and start setup.
@@ -28,13 +28,13 @@ The GAM mailing list / discussion group is hosted on [Google Groups]. You can j
# Chat Room
There is a public chat room hosted in Google Chat. [Instructions to join](https://git.io/gam-chat).
There is a public chat room hosted in Google Chat. [Instructions to join](https://github.com/GAM-team/GAM/wiki/GAM-Public-Chat-Room).
# Author
GAM is maintained by [Jay Lee](mailto:jay0lee@gmail.com). Please direct "how do I?" questions to [Google Groups].
[GAM release]: https://git.io/gamreleases
[GAM release]: https://github.com/GAM-team/GAM/releases
[GitHub Releases]: https://github.com/GAM-team/GAM/releases
[GitHub]: https://github.com/GAM-team/GAM/tree/master
[GitHub Wiki]: https://github.com/GAM-team/GAM/wiki/

View File

@@ -243,6 +243,7 @@ If an item contains spaces, it should be surrounded by ".
<SMTPHostName> ::= <String>
<StudentItem> ::= <EmailAddress>|<UniqueID>|<String>
<TeamDriveID> ::= <String>
<TeamDriveName> ::= <String>
<Timezone> ::= <String>
<Title> ::= <String>
<URI> ::= <String>
@@ -632,6 +633,7 @@ Items, separated by spaces, with spaces, commas or single quotes in the items th
<SchemaNameList> ::= "<SchemaName>(,<SchemaName>)*"
<SerialNumberList> ::= "<SerialNumber>(,<SerialNumber>)*"
<ServiceAccountKeyList> ::= "<ServiceAccountKey>(,<ServiceAccountKey>)*"
<StringList> ::= "<String>(,<String>)*"
<TeamDriveIDList> ::= "<TeamDriveID>(,<TeamDriveID>)*"
<UserFieldNameList> ::= "<UserFieldName>(,<UserFieldName>)*"
<UserList> ::= "<UserItem>(,<UserItem>)*"
@@ -651,7 +653,8 @@ Specify a collection of ChromeOS devices by directly specifying them
(crosfile <FileName>)|
(croscsvfile <FileName>:<FieldName>)|
(crosquery <QueryCrOS>)|
(crosqueries <QueryCrOSList>)
(crosqueries <QueryCrOSList>)|
(cros_ou|cros_ou_and_children <OrgUnitPath>)
## Collections of Users
@@ -680,12 +683,19 @@ Specify a collection of Users by directly specifying them or by specifying items
## Item attributes
<BuildingAttribute> ::=
(address|addresslines <String>)|
(city|locality <String>)|
(country|regioncode <String>)|
(description <String>)|
(floors <FloorNameList>)|
(id <String>)|
(language|languageCode <Language>)|
(latitude <Float>)|
(longitude <Float>)|
(name <String>)
(state|administrativearea <String>)|
(sublocality <String>)|
(zipcode|postalcode <String>)
<CalendarAttribute> ::=
(selected <Boolean>)|(hidden <Boolean>)|(summary <String>)|(colorindex|colorid <CalendarColorIndex>)|(backgroundcolor <ColorValue>)|(foregroundcolor <ColorValue>)|
@@ -862,6 +872,7 @@ Specify a collection of Users by directly specifying them or by specifying items
<UserBasicAttribute>|
<UserMultiAttribute>
gam checkconnection
gam version [check|checkrc|simple|extended] [timeoffset] [location <HostName>]
gam help
@@ -1057,9 +1068,9 @@ 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>) [sendnotifications <Boolean>]
gam calendar <CalendarItem> add <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domain|default [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 ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domain|default
gam calendar <CalendarItem> del|delete id <CalendarACLRuleID>
gam calendar <CalendarItem> showacl
gam calendar <CalendarItem> printacl [todrive]
@@ -1210,6 +1221,76 @@ gam print browsertokens [todrive]
[fields <BrowserTokenFieldNameList>]
[sortheaders]
<CAAAllowedEncryptionStatus> ::=
encryption_unsupported |
encrypted |
unencrypted
<CAAAllowedEncryptionStatusList> ::= "<CAAAllowedEncryptionStatus>(,<CAAAllowedEncryptionStatus>)"
<CAAAllowedDeviceManagementLevel> ::=
basic |
advanced|complete |
none
<CAAAllowedDeviceManagementLevelList> ::= "<CAAAllowedDeviceManagementLevel>(,<CAAAllowedDeviceManagementLevel>)"
<CAACombiningFunction> ::=
and |
or
<CAAIPSubNetwork> ::=
<CIDRnetmask>
<CAAIPSubNetworkList> ::= "<CAAIPSubNetwork>(,<CAAIPSubNetwork>)"
<CAAMember> ::=
user:<EmailAddress> |
serviceAccount:<EmailAddress>
<CAAMemberList> ::= "<CAAMember>(,<CAAMember>)"
<CAAOsType> ::=
DESKTOP_MAC |
DESKTOP_WINDOWS |
DESKTOP_LINUX |
DESKTOP_CHROME_OS |
VERIFIED_DESKTOP_CHROME_OS |
ANDROID |
IOS
<CAAOsConstraint> ::=
<CAAOsType> |
<CAAOsType>:<String>.<String>.<String>
<CAAOsConstraintList> ::= "<CAAOsConstraint>(,<CAAOsConstraint>)"
<CAARegion> ::=
<Character><Character>
<CAARegionList> ::= "<CAARegion>(,<CAARegion>)"
See: https://www.iso.org/obp/ui/#search
<CAADevicePolicyAttribute> ::=
(requirescreenlock <Boolean>) |
(allowedencryptionstatuses <CAAAllowedEncryptionStatusList>) |
(osconstraints <CAAOsConstraintList>) |
(alloweddevicemanagementlevels <CAAAllowedDeviceManagementLevelList>) |
(requireadminapproval <Boolean>) |
(requirecorpowned <Boolean>)
<CAAConditionAttribute> ::=
(ipsubnetworks <CAAIPSubNetworkList>) |
(devicepolicy <CAADevicePolicyAttribute> enddevicepolicy) |
(requiredaccesslevels <StringList>) |
(negate <Boolean>) |
(members <CAARegionList>) |
(regions <CAAMemberList>)
<CAABasicAttribute> ::+
(combiningfunction <CAACombiningFunction>) |
(condition <CAAConditionAttribute>+ endcondition)
gam create caalevel <String> (basic <CAABasicAttribute>+)|(custom <String>)
gam update caalevel <CAALevelName> (basic <CAABasicAttribute>+)|(custom <String>)
gam delete caalevel <CAALevelName>
gam show caalevels
gam print caalevels [todrive]
gam print chatspaces [todrive]
gam print chatmembers space <ChatSpace> [todrive]
gam create chatmessage space <ChatSpace> [thread <String>]
@@ -1224,7 +1305,9 @@ gam update chatmessage name <String>
deprovision_retiring_device|
deprovision_upgrade_transfer|
disable|
reenable
reenable|
pre_provisioned_disable|
pre_provisioned_reenable
gam update cros <CrOSEntity> action <CrOSAction> [acknowledge_device_touch_requirement]
@@ -1242,7 +1325,7 @@ 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>]
gam print cros [todrive] [(query <QueryCrOS>)|(queries <QueryCrOSList>)] [limittoou <OrgUnitItem>]
gam print cros [todrive] [(query <QueryCrOS>)|(queries <QueryCrOSList>)] [limittoou|cros_ou|cros_ou_and_children <OrgUnitItem>]
[orderby <CrOSOrderByFieldName> [ascending|descending]] [nolists|<CrOSListFieldName>*] [listlimit <Number>] [start <Date>] [end <Date>]
[basic|full|allfields] <CrOSFieldName>* [fields <CrOSFieldNameList>] [sortheaders]
gam <CrOSTypeEntity> print
@@ -1250,7 +1333,7 @@ gam <CrOSTypeEntity> print
Summary of printing:
gam print cros
Prints a header row and deviceId for all CrOS devices.
gam <CrOSTypeEntity> print cros
gam <CrOSTypeEntity> print
Prints no header row and deviceId for specified CrOS devices.
gam print cros ... basic|full
Prints a header row and selected fields for specified CrOS devices.
@@ -1264,7 +1347,7 @@ One set of values for all <CrOSListFieldName> fields specified will be output on
The listlimit <Number> argument limits the number of repetitions to <Number>; if not specified or <Number> equals zero, there is no limit.
The start <Date> and end <Date> arguments constrain activeTimeRanges, cpuStatusReports, deviceFiles and systemRamFreeReports to fall within the specified <Dates>.
gam print crosactivity [todrive] [(query <QueryCrOS>)|(queries <QueryCrOSList>)] [limittoou <OrgUnitItem>]
gam print crosactivity [todrive] [(query <QueryCrOS>)|(queries <QueryCrOSList>)] [limittoou|cros_ou|cros_ou_and_children <OrgUnitItem>]
[recentusers] [timeranges] [both] [devicefiles] [all] [listlimit <Number>] [start <Date>] [end <Date>] [delimiter <Character>]
The basic column headers are: deviceId,annotatedAssetId,annotatedLocation,serialNumber,orgUnitPath.
@@ -1350,11 +1433,11 @@ gam print chromehistory channels [todrive]
gam print chromehistory versions [todrive]
[platform <ChromePlatformType>] [channel <ChromeChannelType>]
[filter <String>]
(orderby <ChromeVersionsOrderByFieldName> [ascending|descending])*
(orderby <ChromeVersionsOrderByFieldName> [ascending|descending])*
gam print chromehistory releases [todrive]
[platform <ChromePlatformType>] [channel <ChromeChannelType>] [version <String>]
[filter <String>]
(orderby <ChromeReleasessOrderByFieldName> [ascending|descending])*
(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>)]
@@ -1423,7 +1506,7 @@ 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] [dynamic <QueryDynamicGroup>]
[security|dynamicsecurity] [dynamic <QueryDynamicGroup>]
[memberrestrictions <QueryMemberRestrictions>]
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>
@@ -1488,6 +1571,9 @@ gam update feature <Name> name <Name>
gam delete feature <Name>
gam print features [todrive]
gam show oushareddrives|orgunitshareddrives [ou|org|orgunit <OrgUnitItem>]
gam print oushareddrives|orgunitshareddrives [todrive] [ou|org|orgunit <OrgUnitItem>]
gam create resource <ResourceID> <Name> <ResourceAttribute>*
gam update resource <ResourceID> <ResourceAttribute>*
gam delete resource <ResourceID>
@@ -1760,12 +1846,12 @@ gam <UserTypeEntity> show signature|sig [format]
teammembersonly
gam <UserTypeEntity> create|add teamdrive <Name>
gam <UserTypeEntity> update teamdrive <TeamDriveID> [asadmin] [name <Name>]
gam <UserTypeEntity> update teamdrive <TeamDriveID>|(name <TeamDriveName>) [asadmin] [name <Name>]
[(theme|themeid <String>) | ([customtheme <DriveFileID> <Float> <Float> <Float>] [color <ColorValue>])]
(<TeamDriveRestrictionsSubfieldName> <Boolean>)*
[hidden <Boolean>]
gam <UserTypeEntity> delete teamdrive <TeamDriveID>
gam <UserTypeEntity> show teamdriveinfo <TeamDriveID> [asadmin]
gam <UserTypeEntity> delete teamdrive <TeamDriveID>|(name <TeamDriveName>) [asadmin] [allowitemdeletion|nukefromorbit]
gam <UserTypeEntity> show teamdriveinfo <TeamDriveID>|(name <TeamDriveName>) [asadmin]
gam <UserTypeEntity> show teamdrives [query <QueryTeamDrive>] [asadmin]
gam <UserTypeEntity> print teamdrives [query <QueryTeamDrive>] [todrive] [asadmin]
gam <UserTypeEntity> show teamdrivethemes

View File

@@ -28,7 +28,7 @@ upgrade_only=false
gamversion="latest"
adminuser=""
regularuser=""
gam_glibc_vers="2.31"
gam_glibc_vers="2.35"
while getopts "hd:a:o:b:lp:u:r:v:" OPTION
do
@@ -113,10 +113,9 @@ case $gamos in
done
case $gamarch in
x86_64) gamfile="linux-x86_64-$useglibc.tar.xz";;
arm64|aarch64) gamfile="linux-aarch64-glibc2.28.tar.xz";;
arm|armv7l) gamfile="linux-armv7l-glibc2.28.tar.xz";;
arm64|aarch64) gamfile="linux-aarch64-$useglibc.tar.xz";;
*)
echo_red "ERROR: this installer currently only supports x86_64, arm and arm64 Linux. Looks like you're running on $gamarch. Exiting."
echo_red "ERROR: this installer currently only supports x86_64 and arm64 Linux. Looks like you're running on $gamarch. Exiting."
exit
esac
;;
@@ -128,7 +127,7 @@ case $gamos in
this_macos_ver=$osversion
fi
echo "You are running MacOS $this_macos_ver"
gamfile="macos-x86_64.tar.xz"
gamfile="macos-universal2.tar.xz"
;;
MINGW64_NT*)
gamos="windows"
@@ -223,7 +222,7 @@ trap "rm -rf $temp_archive_dir" EXIT
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 $GHCLIENT $browser_download_url)
(cd "$temp_archive_dir" && curl -# -O -L $GHCLIENT $browser_download_url)
mkdir -p "$target_dir"

View File

@@ -33,12 +33,14 @@ for d in a.datas:
pyz = PYZ(a.pure)
# TODO: fix universal2
target_arch = None
#if sys.platform == "darwin":
# target_arch="universal2"
#else:
# target_arch=None
if sys.platform == "darwin":
target_arch="universal2"
else:
target_arch=None
# use strip on all non-Windows platforms
strip = not sys.platform == 'win32'
exe = EXE(pyz,
a.scripts,
@@ -47,7 +49,7 @@ exe = EXE(pyz,
a.datas,
name='gam',
debug=False,
strip=None,
strip=strip,
upx=False,
target_arch=target_arch,
console=True)

View File

@@ -55,6 +55,7 @@ from gam import auth
from gam import controlflow
from gam import display
from gam import fileutils
from gam.gapi import caa as gapi_caa
from gam.gapi import calendar as gapi_calendar
from gam.gapi import cloudidentity as gapi_cloudidentity
from gam.gapi import cbcm as gapi_cbcm
@@ -64,6 +65,7 @@ from gam.gapi import chromemanagement as gapi_chromemanagement
from gam.gapi import chromepolicy as gapi_chromepolicy
from gam.gapi.cloudidentity import devices as gapi_cloudidentity_devices
from gam.gapi.cloudidentity import groups as gapi_cloudidentity_groups
from gam.gapi.cloudidentity import orgunits as gapi_cloudidentity_orgunits
from gam.gapi.cloudidentity import userinvitations as gapi_cloudidentity_userinvitations
from gam.gapi import contactdelegation as gapi_contactdelegation
from gam.gapi.directory import asps as gapi_directory_asps
@@ -80,6 +82,7 @@ from gam.gapi.directory import resource as gapi_directory_resource
from gam.gapi.directory import roles as gapi_directory_roles
from gam.gapi.directory import roleassignments as gapi_directory_roleassignments
from gam.gapi.directory import users as gapi_directory_users
from gam.gapi.drive import drives as gapi_drive_drives
from gam.gapi import licensing as gapi_licensing
from gam.gapi import siteverification as gapi_siteverification
from gam.gapi import errors as gapi_errors
@@ -716,8 +719,11 @@ def doGAMCheckForUpdates(forceCheck=False):
continue_on_error=True,
display_errors=forceCheck)
return
except (httplib2.HttpLib2Error, httplib2.ServerNotFoundError, RuntimeError,
socket.timeout):
except (httplib2.HttpLib2Error,
httplib2.ServerNotFoundError,
RuntimeError,
ConnectionError,
TimeoutError):
return
@@ -743,6 +749,71 @@ def getOSPlatform():
return f'{myos} {pltfrm}'
def checkConnection():
hosts = [
'api.github.com',
'raw.githubusercontent.com',
'gam-shortn.appspot.com',
'accounts.google.com',
'oauth2.googleapis.com',
'www.googleapis.com',
]
api_hosts = []
for api in API_VER_MAPPING:
api = API_NAME_MAPPING.get(api, api)
api = f'{api}.googleapis.com'
if api not in api_hosts and api not in hosts:
api_hosts.append(api)
api_hosts.sort()
hosts.extend(api_hosts)
httpc = transport.create_http(timeout=10)
httpc.follow_redirects = False
headers = {'user-agent': GAM_INFO}
okay = createGreenText('OK')
not_okay = createRedText('ERROR')
gen_firewall = 'You may have security software or a firewall on your machine or network that is preventing GAM from making a secure connection to this host. Check your network configuration or try running GAM on a hotspot or home network to see if the problem exists only on your organization\'s network.'
host_count = len(hosts)
try_count = 0
success_count = 0
for host in hosts:
try_count += 1
check_line = f'Checking {host} ({try_count}/{host_count})...'
sys.stdout.write(f'{check_line:<60}')
sys.stdout.flush()
try:
httpc.request(f'https://{host}/', 'HEAD', headers=headers)
success_count += 1
print(okay)
except BrokenPipeError:
print(f'{not_okay}\n Broken pipe. {gen_firewall}')
except ConnectionAbortedError:
print(f'{not_okay}\n Connection aborted. {gen_firewall}')
except ConnectionRefusedError:
print(f'{not_okay}\n Connection refused. {gen_firewall}')
except ConnectionResetError:
print(f'{not_okay}\n Connection reset by peer. {gen_firewall}')
except httplib2.error.ServerNotFoundError:
print(f'{not_okay}\n Failed to find server. Your DNS is probably misconfigured.')
except ssl.SSLError as e:
if e.reason == 'SSLV3_ALERT_HANDSHAKE_FAILURE':
print(f'{not_okay}\n GAM expects to connect with TLS 1.3 or newer and that failed. If your firewall / proxy server is not compatible with TLS 1.3 then you can tell GAM to allow TLS 1.2 by setting the GAM_TLS_MIN_VERSION=TLSv1_2 environment variable.')
elif e.reason == 'CERTIFICATE_VERIFY_FAILED':
print(f'{not_okay}\n Certificate verification failed. If you are behind a firewall / proxy server that does TLS / SSL inspection you may need to point GAM at your certificate authority file by setting the GAM_CA_FILE=/path/to/your/certauth.pem environment variable.')
elif e.strerror.startswith('TLS/SSL connection has been closed'):
print(f'{not_okay}\n TLS connection was closed. {gen_firewall}')
else:
print(f'{not_okay}\n {e.reason}: {str(e)}')
except TimeoutError:
print(f'{not_okay}\n Timed out trying to connect to host. {gen_firewall}')
except Exception as e:
# include the exception class so we know what to catch in the future
print(f'{not_okay}\n {type(e).__name__} - {str(e)}')
print()
if success_count == host_count:
print(createGreenText('All hosts passed!'))
else:
controlflow.system_error_exit(3, createYellowText('Some hosts failed to connect! Please follow the recommendations for those hosts to correct any issues and try again.'))
def doGAMVersion(checkForArgs=True):
force_check = extended = simple = timeOffset = False
testLocation = 'admin.googleapis.com'
@@ -848,7 +919,9 @@ def _getSvcAcctData():
controlflow.system_error_exit(6, None)
GM_Globals[GM_OAUTH2SERVICE_JSON_DATA] = json.loads(json_string)
jwt_apis = ['chat'] # APIs which can handle OAuthless JWT tokens
jwt_apis = ['chat',
'cloudresourcemanager',
'accesscontextmanager'] # APIs which can handle OAuthless JWT tokens
def getSvcAcctCredentials(scopes, act_as, api=None):
try:
_getSvcAcctData()
@@ -2242,6 +2315,7 @@ def doGetCourseInfo():
COURSE_ARGUMENT_TO_PROPERTY_MAP = {
'alternatelink': 'alternateLink',
'calendarid': 'calendarId',
'coursegroupemail': 'courseGroupEmail',
'coursematerialsets': 'courseMaterialSets',
'coursestate': 'courseState',
@@ -2249,7 +2323,9 @@ COURSE_ARGUMENT_TO_PROPERTY_MAP = {
'description': 'description',
'descriptionheading': 'descriptionHeading',
'enrollmentcode': 'enrollmentCode',
'gradebooksettings': 'gradebookSettings',
'guardiansenabled': 'guardiansEnabled',
'heading': 'descriptionHeading',
'id': 'id',
'name': 'name',
'ownerid': 'ownerId',
@@ -2730,7 +2806,7 @@ def printDriveSettings(users):
display.write_csv_file(csvRows, titles, 'User Drive Settings', todrive)
def getTeamDriveThemes(users):
def getSharedDriveThemes(users):
for user in users:
user, drive = buildDrive3GAPIObject(user)
if not drive:
@@ -7009,6 +7085,8 @@ def getUserAttributes(i, cd, updateCmd):
if schemaValue['type'] == 'custom':
schemaValue['customType'] = sys.argv[i]
i += 1
else:
schemaValue['type'] = 'work'
schemaValue['value'] = sys.argv[i]
if schemaValue['value'] or multivalue != 'multinonempty':
body[up][schemaName][fieldName].append(schemaValue)
@@ -7118,6 +7196,7 @@ def enableGAMProjectAPIs(GAMProjectAPIs,
f' Project: {projectId}, Enable {jcount} APIs{currentCount(i, count)}'
)
j = 0
tried_universal_tos = False
for api in apis:
service_name = f'projects/{projectId}/services/{api}'
j += 1
@@ -7128,19 +7207,26 @@ def enableGAMProjectAPIs(GAMProjectAPIs,
throw_reasons=[
gapi_errors.ErrorReason.FAILED_PRECONDITION,
gapi_errors.ErrorReason.FORBIDDEN,
gapi_errors.ErrorReason.PERMISSION_DENIED
gapi_errors.ErrorReason.PERMISSION_DENIED,
gapi_errors.ErrorReason.FOUR_O_O,
],
retry_reasons=[gapi_errors.ErrorReason.INTERNAL_SERVER_ERROR],
name=service_name)
print(f' API: {api}, Enabled{currentCount(j, jcount)}')
break
except gapi_errors.GapiFailedPreconditionError as e:
except (gapi_errors.GapiFailedPreconditionError,
googleapiclient.errors.HttpError) as e:
if hasattr(e, 'reason'):
msg = e.reason
else:
msg = str(e)
if 'terms of service' in msg.lower() and not tried_universal_tos:
msg = '''You need to agree to the Google Cloud Terms of Service at:\n\n https://console.developers.google.com'''
tried_universal_tos = True
print(
f'\nThere was an error enabling {api}. Please resolve error as described below:'
)
print()
print(f'\n{str(e)}\n')
print()
print(f'\n\n{msg}\n\n')
input(
'Press enter once resolved and we will try enabling the API again.'
)
@@ -7540,7 +7626,7 @@ def doCreateProject():
create_operation = gapi.call(crm.projects(), 'create', body=body)
operation_name = create_operation['name']
time.sleep(8) # Google recommends always waiting at least 5 seconds
for i in range(1, 5):
for i in range(1, 10):
print('Checking project status...')
status = gapi.call(crm.operations(), 'get', name=operation_name)
if 'error' in status:
@@ -7599,7 +7685,7 @@ and accept the Terms of Service (ToS). As soon as you've accepted the ToS popup,
controlflow.system_error_exit(1, status)
if status.get('done', False):
break
sleep_time = i**2
sleep_time = min(2**i, 60)
print(f'Project still being created. Sleeping {sleep_time} seconds')
time.sleep(sleep_time)
if create_again:
@@ -7839,14 +7925,13 @@ def doCreateOrRotateServiceAccountKeys(iam=None,
body={'publicKeyData': publicKeyData})
break
except googleapiclient.errors.HttpError as err:
if hasattr(err, 'error_details') and \
err.error_details == 'The given public key already exists.':
print('WARNING: that key already exists.')
result = {'name': oldPrivateKeyId}
break
elif hasattr(err, 'error_details'):
controlflow.system_error_exit(
4, err.error_details)
if hasattr(err, 'error_details'):
if err.error_details == 'The given public key already exists.':
print('WARNING: that key already exists.')
result = {'name': oldPrivateKeyId}
break
controlflow.system_error_exit(
4, err.error_details)
else:
controlflow.system_error_exit(
4, err)
@@ -8006,10 +8091,17 @@ def doPrintShowProjects(csvFormat):
display.write_csv_file(csvRows, titles, 'Projects', todrive)
def doGetTeamDriveInfo(users):
teamDriveId = sys.argv[5]
def getSharedDriveId(i):
driveId = sys.argv[i]
if driveId.lower() == 'name':
i += 1
driveId = gapi_drive_drives.drive_name_to_id(sys.argv[i])
return (i+1, driveId)
def doGetSharedDriveInfo(users):
i, driveId = getSharedDriveId(5)
useDomainAdminAccess = False
i = 6
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'asadmin':
@@ -8017,7 +8109,7 @@ def doGetTeamDriveInfo(users):
i += 1
else:
controlflow.invalid_argument_exit(myarg,
'gam <users> show teamdrive')
'gam <users> show shareddrive')
for user in users:
drive = buildGAPIServiceObject('drive3', user)
if not drive:
@@ -8025,13 +8117,15 @@ def doGetTeamDriveInfo(users):
continue
result = gapi.call(drive.drives(),
'get',
driveId=teamDriveId,
driveId=driveId,
useDomainAdminAccess=useDomainAdminAccess,
fields='*')
if useDomainAdminAccess and 'orgUnitId' in result:
result['orgUnit'] = gapi_directory_orgunits.orgunit_from_orgunitid(f'id:{result["orgUnitId"]}')
display.print_json(result)
def doCreateTeamDrive(users):
def doCreateSharedDrive(users):
body = {'name': sys.argv[5]}
i = 6
while i < len(sys.argv):
@@ -8041,7 +8135,7 @@ def doCreateTeamDrive(users):
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam <users> create teamdrive')
'gam <users> create shareddrive')
for user in users:
drive = buildGAPIServiceObject('drive3', user)
if not drive:
@@ -8053,7 +8147,7 @@ def doCreateTeamDrive(users):
requestId=requestId,
body=body,
fields='id')
print(f'Created Team Drive {body["name"]} with id {result["id"]}')
print(f'Created Shared Drive {body["name"]} with id {result["id"]}')
TEAMDRIVE_RESTRICTIONS_MAP = {
@@ -8064,17 +8158,20 @@ TEAMDRIVE_RESTRICTIONS_MAP = {
}
def doUpdateTeamDrive(users):
teamDriveId = sys.argv[5]
def doUpdateSharedDrive(users):
i, driveId = getSharedDriveId(5)
body = {}
useDomainAdminAccess = False
change_hide = None
i = 6
orgUnit = None
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'name':
body['name'] = sys.argv[i + 1]
i += 2
elif myarg in ['ou', 'org', 'orgunit']:
orgUnit = sys.argv[i+1]
i += 2
elif myarg == 'theme':
body['themeId'] = sys.argv[i + 1]
i += 2
@@ -8092,9 +8189,9 @@ def doUpdateTeamDrive(users):
elif myarg == 'asadmin':
useDomainAdminAccess = True
i += 1
elif myarg in ['ou', 'org', 'orgunit']:
body['orgUnitId'] = gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])
i += 2
# elif myarg in ['ou', 'org', 'orgunit']:
# body['orgUnitId'] = gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])
# i += 2
elif myarg in ['hidden']:
if getBoolean(sys.argv[i+1], myarg):
change_hide = 'hide'
@@ -8109,8 +8206,8 @@ def doUpdateTeamDrive(users):
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam <users> update teamdrive')
if not body and not change_hide:
'gam <users> update shareddrive')
if not body and not change_hide and not orgUnit:
controlflow.system_error_exit(
4, 'nothing to update. Need at least a name argument.')
for user in users:
@@ -8122,7 +8219,7 @@ def doUpdateTeamDrive(users):
'update',
useDomainAdminAccess=useDomainAdminAccess,
body=body,
driveId=teamDriveId,
driveId=driveId,
fields='id',
soft_errors=True)
if not result:
@@ -8130,15 +8227,19 @@ def doUpdateTeamDrive(users):
if change_hide:
ch_result = gapi.call(drive.drives(),
change_hide,
driveId=teamDriveId,
driveId=driveId,
fields='id',
soft_errors=True)
print(f'Updated Team Drive {teamDriveId}')
if orgUnit:
gapi_cloudidentity_orgunits.move_shared_drive(driveId,
orgUnit)
print(f'Updated Shared Drive {driveId}')
def printShowTeamDrives(users, csvFormat):
def printShowSharedDrives(users, csvFormat):
todrive = False
useDomainAdminAccess = False
q = None
get_orgunits = True
i = 5
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
@@ -8151,13 +8252,18 @@ def printShowTeamDrives(users, csvFormat):
elif myarg == 'query':
q = sys.argv[i + 1]
i += 2
elif myarg == 'noorgunits':
get_orgunits = False
i += 1
else:
controlflow.invalid_argument_exit(
myarg, f"gam {['show', 'print'][csvFormat]} teamdrives")
myarg, f"gam {['show', 'print'][csvFormat]} shareddrives")
tds = []
titles = []
if get_orgunits and useDomainAdminAccess:
ou_map = gapi_directory_orgunits.orgid_to_org_map()
for user in users:
sys.stderr.write(f'Getting Team Drives for {user}\n')
sys.stderr.write(f'Getting Shared Drives for {user}\n')
user, drive = buildDrive3GAPIObject(user)
if not drive:
continue
@@ -8172,12 +8278,16 @@ def printShowTeamDrives(users, csvFormat):
continue
for td in results:
td = utils.flatten_json(td)
if get_orgunits and useDomainAdminAccess:
td_ouid = td.get('orgUnitId')
if td_ouid:
td['orgUnit'] = ou_map.get(f'id:{td_ouid}', 'Unknown')
for key in td:
if key not in titles:
titles.append(key)
tds.append(td)
if csvFormat:
display.write_csv_file(tds, titles, 'Team Drives', todrive)
display.write_csv_file(tds, titles, 'Shared Drives', todrive)
else:
for td in tds:
name = td.pop('name')
@@ -8187,17 +8297,38 @@ def printShowTeamDrives(users, csvFormat):
print()
def doDeleteTeamDrive(users):
teamDriveId = sys.argv[5]
def doDeleteSharedDrive(users):
_, driveId = getSharedDriveId(5)
allowItemDeletion = False
useDomainAdminAccess = False
i = 6
if driveId.lower().startswith('name'):
driveId = gapi_drive_drives.drive_name_to_id(sys.argv[i])
i += 1
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['nukefromorbit', 'allowitemdeletion']:
print("I say we take off and nuke the entire site from orbit. It's the only way to be sure...")
print('(deleting the shared drive and all files on it...)')
allowItemDeletion = True
useDomainAdminAccess = True
i += 1
elif myarg == 'asadmin':
useDomainAdminAccess = True
i += 1
else:
controlflow.invalid_argument_exit(
myarg, 'gam delete shareddrive')
for user in users:
user, drive = buildDrive3GAPIObject(user)
if not drive:
continue
print(f'Deleting Team Drive {teamDriveId}')
print(f'Deleting Shared Drive {driveId}')
gapi.call(drive.drives(),
'delete',
driveId=teamDriveId,
driveId=driveId,
allowItemDeletion=allowItemDeletion,
useDomainAdminAccess=useDomainAdminAccess,
soft_errors=True)
@@ -8986,8 +9117,9 @@ def doGetUserInfo(user_email=None):
if isinstance(user['customSchemas'][schema][field], list):
print(f' {field}:')
for an_item in user['customSchemas'][schema][field]:
print(f' type: {an_item["type"]}')
if an_item['type'] == 'custom':
an_type = an_item.get('type', 'work')
print(f' type: {an_type}')
if an_type == 'custom':
print(
f' customType: {an_item["customType"]}')
print(f' value: {an_item["value"]}')
@@ -10232,8 +10364,13 @@ def getUsersToModify(entity_type=None,
elif entity_type == 'cros':
users = entity.replace(',', ' ').split()
entity = 'cros'
elif entity_type in ['crosquery', 'crosqueries', 'cros_sn']:
if entity_type == 'cros_sn':
elif entity_type in ['crosquery', 'crosqueries', 'cros_sn', 'cros_ou', 'cros_ou_and_children']:
orgUnitPath = includeChildOrgunits = None
if entity_type in {'cros_ou', 'cros_ou_and_children'}:
orgUnitPath = entity
includeChildOrgunits = entity_type == 'cros_ou_and_children'
queries = [None]
elif entity_type == 'cros_sn':
queries = [f'id:{sn}' for sn in shlexSplitList(entity)]
elif entity_type == 'crosqueries':
queries = shlexSplitList(entity)
@@ -10250,9 +10387,11 @@ def getUsersToModify(entity_type=None,
'list',
'chromeosdevices',
page_message=page_message,
query=query,
customerId=GC_Values[GC_CUSTOMER_ID],
fields='nextPageToken,chromeosdevices(deviceId)',
query=query)
orgUnitPath=orgUnitPath,
includeChildOrgunits=includeChildOrgunits,
fields='nextPageToken,chromeosdevices(deviceId)')
for member in members:
deviceId = member['deviceId']
if deviceId not in usersSet:
@@ -10445,6 +10584,11 @@ OAUTH2_SCOPES = [
'subscopes': ['readonly'],
'scopes': 'https://www.googleapis.com/auth/cloud-identity.groups'
},
{
'name': 'Cloud Identity - OrgUnits',
'subscopes': ['readonly'],
'scopes': 'https://www.googleapis.com/auth/cloud-identity.orgunits',
},
{
'name': 'Cloud Identity - User Invitations',
'subscopes': ['readonly'],
@@ -11306,6 +11450,9 @@ def ProcessGAMCommand(args):
elif command == 'version':
doGAMVersion()
sys.exit(0)
elif command in ['checkconnection', 'checkconn']:
checkConnection()
sys.exit(0)
elif command == 'create':
argument = sys.argv[2].lower()
if argument == 'user':
@@ -11366,6 +11513,8 @@ def ProcessGAMCommand(args):
gapi_directory_printers.create()
elif argument in ['chatmessage']:
gapi_chat.create_message()
elif argument in ['caalevel']:
gapi_caa.create_access_level()
else:
controlflow.invalid_argument_exit(argument, 'gam create')
sys.exit(0)
@@ -11430,6 +11579,8 @@ def ProcessGAMCommand(args):
gapi_directory_printers.update()
elif argument in ['chatmessage']:
gapi_chat.update_message()
elif argument in ['caalevel']:
gapi_caa.update_access_level()
else:
controlflow.invalid_argument_exit(argument, 'gam update')
sys.exit(0)
@@ -11570,6 +11721,8 @@ def ProcessGAMCommand(args):
gapi_chromepolicy.delete_policy()
elif argument == 'chatmessage':
gapi_chat.delete_message()
elif argument == 'caalevel':
gapi_caa.delete_access_level()
else:
controlflow.invalid_argument_exit(argument, 'gam delete')
sys.exit(0)
@@ -11691,6 +11844,10 @@ def ProcessGAMCommand(args):
gapi_chat.print_spaces()
elif argument in ['chatmembers']:
gapi_chat.print_members()
elif argument in ['caalevels']:
gapi_caa.printshow_access_levels(True)
elif argument in ['oushareddrives', 'orgunitshareddrives']:
gapi_cloudidentity_orgunits.printshow_orgunit_shared_drives(True)
else:
controlflow.invalid_argument_exit(argument, 'gam print')
sys.exit(0)
@@ -11721,6 +11878,10 @@ def ProcessGAMCommand(args):
gapi_chromepolicy.printshow_policies()
elif argument == 'crostelemetry':
gapi_chromemanagement.printShowCrosTelemetry('show')
elif argument in ['caalevels']:
gapi_caa.printshow_access_levels(False)
elif argument in ['oushareddrives', 'orgunitshareddrives']:
gapi_cloudidentity_orgunits.printshow_orgunit_shared_drives(False)
else:
controlflow.invalid_argument_exit(argument, 'gam show')
sys.exit(0)
@@ -11879,8 +12040,8 @@ def ProcessGAMCommand(args):
gapi_calendar.showCalSettings(users)
elif showWhat == 'drivesettings':
printDriveSettings(users)
elif showWhat == 'teamdrivethemes':
getTeamDriveThemes(users)
elif showWhat in ['teamdrivethemes', 'shareddrivethemes']:
getSharedDriveThemes(users)
elif showWhat == 'drivefileacl':
showDriveFileACL(users)
elif showWhat == 'filelist':
@@ -11923,10 +12084,10 @@ def ProcessGAMCommand(args):
printShowFilters(users, False)
elif showWhat in ['forwardingaddress', 'forwardingaddresses']:
printShowForwardingAddresses(users, False)
elif showWhat in ['teamdrive', 'teamdrives']:
printShowTeamDrives(users, False)
elif showWhat in ['teamdriveinfo']:
doGetTeamDriveInfo(users)
elif showWhat in shared_drive_values:
printShowSharedDrives(users, False)
elif showWhat in ['shareddriveinfo', 'teamdriveinfo']:
doGetSharedDriveInfo(users)
elif showWhat in ['contactdelegate', 'contactdelegates']:
gapi_contactdelegation.print_(users, False)
elif showWhat in ['holds', 'vaultholds']:
@@ -11957,8 +12118,8 @@ def ProcessGAMCommand(args):
printShowSmime(users, True)
elif printWhat in ['token', 'tokens', 'oauth', '3lo']:
printShowTokens(5, 'users', users, True)
elif printWhat in ['teamdrive', 'teamdrives']:
printShowTeamDrives(users, True)
elif printWhat in shared_drive_values:
printShowSharedDrives(users, True)
elif printWhat in ['contactdelegate', 'contactdelegates']:
gapi_contactdelegation.print_(users, True)
elif printWhat in ['labels']:
@@ -12041,8 +12202,8 @@ def ProcessGAMCommand(args):
deleteSendAs(users)
elif delWhat == 'smime':
deleteSmime(users)
elif delWhat == 'teamdrive':
doDeleteTeamDrive(users)
elif delWhat in shared_drive_values:
doDeleteSharedDrive(users)
elif delWhat == 'contactdelegate':
gapi_contactdelegation.delete(users)
else:
@@ -12075,8 +12236,8 @@ def ProcessGAMCommand(args):
addUpdateSendAs(users, 5, True)
elif addWhat == 'smime':
addSmime(users)
elif addWhat == 'teamdrive':
doCreateTeamDrive(users)
elif addWhat in shared_drive_values:
doCreateSharedDrive(users)
elif addWhat == 'contactdelegate':
gapi_contactdelegation.create(users)
else:
@@ -12117,8 +12278,8 @@ def ProcessGAMCommand(args):
addUpdateSendAs(users, 5, False)
elif updateWhat == 'smime':
updateSmime(users)
elif updateWhat == 'teamdrive':
doUpdateTeamDrive(users)
elif updateWhat in shared_drive_values:
doUpdateSharedDrive(users)
else:
controlflow.invalid_argument_exit(updateWhat,
'gam <users> update')

View File

@@ -18,7 +18,7 @@
"""GAM is a command line tool which allows Administrators to control their Google Workspace domain and accounts.
With GAM you can programmatically create users, turn on/off services for users like POP and Forwarding and much more.
For more information, see https://git.io/gam
For more information, see https://jaylee.us/gam
"""
import sys

View File

@@ -24,7 +24,10 @@ from gam import controlflow
from gam import display
from gam import fileutils
from gam import transport
from gam.var import GM_Globals, GM_WINDOWS
from gam.var import (GC_CA_FILE,
GC_Values,
GM_Globals,
GM_WINDOWS)
from gam import utils
@@ -633,7 +636,10 @@ class _ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
parsed_params = parse_qs(parsed_url.query)
code = parsed_params.get('code', [None])[0]
try:
self.fetch_token(code=code)
fetch_args = {'code': code}
if GC_Values.get('GC_CA_FILE'):
fetch_args['verify'] = GC_Values.get('GC_CA_FILE')
self.fetch_token(**fetch_args)
break
except Exception as e:
if not userInput:

274
src/gam/gapi/caa.py Normal file
View File

@@ -0,0 +1,274 @@
import string
import sys
import googleapiclient.errors
import gam
from gam.var import *
from gam import controlflow
from gam import display
from gam import gapi
from gam import utils
from gam.gapi import errors as gapi_errors
from gam.gapi import cloudresourcemanager as gapi_crm
THROW_REASONS = [gapi_errors.ErrorReason.FOUR_O_THREE]
def _gen_role_error(caa):
sa_email = caa._http.credentials.signer_email
role_error = f'Please grant service account {sa_email} the Access Context Manager Editor role to your GCP organization.'
controlflow.system_error_exit(2, role_error)
def build():
return gam.buildGAPIServiceObject('accesscontextmanager',
act_as=None)
def get_access_policy(caa=None):
if not caa:
caa = build()
parent = gapi_crm.get_org_id()
if not parent:
_gen_role_error(caa)
try:
aps = gapi.get_all_pages(caa.accessPolicies(),
'list',
'accessPolicies',
throw_reasons=THROW_REASONS,
parent=parent,
fields='accessPolicies(name,title)')
except googleapiclient.errors.HttpError:
_gen_role_error(caa)
if not aps:
controlflow.system_error_exit(2, 'You don\'t seem to have any access policies. That is odd.')
elif len(aps) == 1:
return aps[0]['name']
for ap in aps:
if ap.get('title') == 'Access policy created in Cloud Identity Console':
return ap['name']
controlflow.system_error_exit(2, ' Could not find a org level access policy. That is odd.')
def printshow_access_levels(csvFormat):
caa = build()
ap_name = get_access_policy(caa)
if csvFormat:
todrive = False
csvRows = []
titles = ['name', 'title']
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if csvFormat and myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i],
f"gam {['show', 'print'][csvFormat]} caalevels")
try:
levels = gapi.get_all_pages(caa.accessPolicies().accessLevels(),
'list',
'accessLevels',
throw_reasons=THROW_REASONS,
parent=ap_name,
accessLevelFormat='CEL', fields='*')
except googleapiclient.errors.HttpError:
_gen_role_error(caa)
if not csvFormat:
for level in levels:
display.print_json(level)
print()
else:
for level in levels:
display.add_row_titles_to_csv_file(
utils.flatten_json(level),
csvRows, titles)
display.write_csv_file(csvRows, titles, 'CAA Levels', todrive)
def build_os_constraints(constraints):
consts_obj = []
constraints = constraints.upper().split(',')
valid_os_types = ['DESKTOP_MAC', 'DESKTOP_WINDOWS', 'DESKTOP_LINUX',
'DESKTOP_CHROME_OS', 'VERIFIED_DESKTOP_CHROME_OS', 'ANDROID', 'IOS']
for constraint in constraints:
new_const = {}
if ':' in constraint:
new_const['osType'], new_const['minimumVersion'] = constraint.split(':')
else:
new_const['osType'] = constraint
if new_const['osType'] not in valid_os_types:
controlflow.system_error_exit(2, f'expected os type of {", ".join(valid_os_types)} got {new_const["osType"]}')
if new_const['osType'] == 'VERIFIED_DESKTOP_CHROME_OS':
new_const['osType'] = 'DESKTOP_CHROME_OS'
new_const['requireVerifiedChromeOs'] = True
consts_obj.append(new_const)
return consts_obj
def build_device_policy(i, schemas):
device_policy = {}
while True:
myarg = sys.argv[i].replace('_', '').lower()
if myarg == 'requirescreenlock':
device_policy['requireScreenLock'] = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg == 'allowedencryptionstatuses':
allowed_statuses = gapi.get_enum_values_minus_unspecified(schemas["DevicePolicy"]["properties"]["allowedEncryptionStatuses"]["items"]["enum"])
device_policy['allowedEncryptionStatuses'] = sys.argv[i+1].upper().split(',')
for status in device_policy['allowedEncryptionStatuses']:
if status not in allowed_statuses:
controlflow.system_error_exit(2, f'expected encryption status of {", ".join(allowed_statuses)} got {status}')
i += 2
elif myarg == 'osconstraints':
device_policy['osConstraints'] = build_os_constraints(sys.argv[i+1])
i += 2
elif myarg == 'alloweddevicemanagementlevels':
allowed_levels = gapi.get_enum_values_minus_unspecified(schemas["DevicePolicy"]["properties"]["allowedDeviceManagementLevels"]["items"]["enum"])
device_policy['allowedDeviceManagementLevels'] = sys.argv[i+1].upper().split(',')
for level in device_policy['allowedDeviceManagementLevels']:
if level == 'ADVANCED':
level = 'COMPLETE'
if level not in allowed_levels:
controlflow.system_error_exit(2, f'expected device management level of {", ".join(allowed_levels)} got {level}')
i += 2
elif myarg == 'requireadminapproval':
device_policy['requireAdminApproval'] = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg == 'requirecorpowned':
device_policy['requireCorpOwned'] = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg == 'enddevicepolicy':
i += 1
break
else:
controlflow.invalid_argument_exit(myarg, 'gam create/update caalevel')
return i, device_policy
def build_condition(i, schemas):
condition = {}
while True:
myarg = sys.argv[i].replace('_', '').lower()
if myarg == 'ipsubnetworks':
condition['ipSubnetworks'] = sys.argv[i+1].split(',')
i += 2
elif myarg == 'devicepolicy':
i += 1
i, condition['devicePolicy'] = build_device_policy(i, schemas)
elif myarg == 'requiredaccesslevels':
condition['requiredAccessLevels'] = sys.argv[i+1].split(',')
i += 2
elif myarg == 'negate':
condition['negate'] = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg == 'members':
condition['members'] = sys.argv[i+1].split(',')
i += 2
elif myarg == 'regions':
condition['regions'] = sys.argv[i+1].upper().split(',')
i += 2
elif myarg == 'endcondition':
i += 1
break
else:
controlflow.invalid_argument_exit(myarg, 'gam create/update caalevel')
return i, condition
def build_basic_level(i, schemas):
basic_level = {'conditions': []}
valid_functions = gapi.get_enum_values_minus_unspecified(schemas['BasicLevel']['properties']['combiningFunction']['enum'])
while i < len(sys.argv):
myarg = sys.argv[i].replace('_', '').lower()
if myarg == 'combiningfunction':
combiningFunction = sys.argv[i+1].upper()
if combiningFunction not in valid_functions:
controlflow.system_error_exit(2, f'expected combining function of {",".join(valid_functions)} got {combiningFunction}')
basic_level['combiningFunction'] = combiningFunction
i += 2
elif myarg == 'condition':
i += 1
i, condition = build_condition(i, schemas)
basic_level['conditions'].append(condition)
else:
controlflow.invalid_argument_exit(myarg, 'gam create/update caalevel')
return i, basic_level
def build_caa_level(i, caa, body):
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'basic':
schemas = caa._rootDesc['schemas']
i += 1
i, body['basic'] = build_basic_level(i, schemas)
elif myarg == 'custom':
body['custom'] = {'expr': {'expression': sys.argv[i+1], 'title': 'expr'}}
i += 2
elif myarg == 'description':
body['description'] = sys.argv[i+1]
i += 2
else:
controlflow.invalid_argument_exit(myarg, 'gam create/update caalevel')
def create_access_level():
caa = build()
ap_name = get_access_policy(caa)
title = sys.argv[3].replace(' ', '_')
allowed_title_chars = string.ascii_letters + string.digits + '_'
name = ''.join([c for c in title if c in allowed_title_chars])[:50]
name = f'{ap_name}/accessLevels/{name}'
body = {
'name': name,
'title': title,
}
build_caa_level(4, caa, body)
print(f'Creating access level {name}...')
try:
gapi.call(caa.accessPolicies().accessLevels(),
'create',
throw_reasons=THROW_REASONS,
parent=ap_name,
body=body)
except googleapiclient.errors.HttpError:
_gen_role_error(caa)
def get_access_level_name(i, caa):
name = sys.argv[i]
if not name.startswith('accessPolicies/'):
ap_name = get_access_policy(caa)
name = f'{ap_name}/accessLevels/{name}'
return name
def update_access_level():
caa = build()
name = get_access_level_name(3, caa)
body = {}
build_caa_level(4, caa, body)
updateMask = ','.join(body.keys())
print(f'Updating access level {name}...')
try:
gapi.call(caa.accessPolicies().accessLevels(),
'patch',
throw_reasons=THROW_REASONS,
name=name,
updateMask=updateMask,
body=body)
except googleapiclient.errors.HttpError:
_gen_role_error(caa)
def delete_access_level():
caa = build()
name = get_access_level_name(3, caa)
print(f'Deleting access level {name}...')
try:
gapi.call(caa.accessPolicies().accessLevels(),
'delete',
name=name)
except googleapiclient.errors.HttpError:
_gen_role_error(caa)

View File

@@ -1,9 +1,12 @@
import gam
from gam.var import GC_Values, GC_ENABLE_DASA
def build(api='cloudidentity'):
return gam.buildGAPIObject(api)
def build_dwd(api='cloudidentity'):
# If we are using DASA we don't need to use DwD.
if GC_Values[GC_ENABLE_DASA]:
return gam.buildGAPIObject(api)
admin = gam._get_admin_email()
return gam.buildGAPIServiceObject(api, admin, True)

View File

@@ -253,6 +253,13 @@ def update_state():
def print_():
# This function is rather messy thanks to
# https://github.com/GAM-team/GAM/issues/1534
# I'd prefer to keep it all in this function for now but if:
# - we find other list() operations that also hit this bug OR
# - it looks like this issue is going to exist on Google's side
# for a long time.
# I'll enterain some cleanup here to "functionalize" (yuck) all of this.
ci = gapi_cloudidentity.build_dwd()
customer = _get_device_customerid()
parent = 'devices/-'
@@ -260,6 +267,8 @@ def print_():
get_device_users = True
view = None
orderByList = []
# default sort order needed by our 1 hour bug workaround
orderBy = 'create_time'
titles = []
csvRows = []
todrive = False
@@ -319,26 +328,97 @@ def print_():
}
if orderByList:
orderBy = ','.join(orderByList)
else:
orderBy = None
devices = []
custom_device_filter = bool(device_filter)
# we store the devices in a dict keyed by name which is a unique ID.
# that way when we get duplicate devices we just overwrite the name
# with the latest copy we saw.
devices = {}
page_message = gapi.got_total_items_msg(view_name_map[view], '...\n')
devices += gapi.get_all_pages(ci.devices(), 'list', 'devices',
customer=customer, page_message=page_message,
pageSize=100, filter=device_filter, view=view, orderBy=orderBy)
pageToken = None
newest_device_date = ''
total_items = 0
while True:
try:
a_page = gapi.call(ci.devices(),
'list',
customer=customer,
pageSize=100,
pageToken=pageToken,
filter=device_filter,
view=view,
orderBy=orderBy,
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O])
except googleapiclient.errors.HttpError:
sys.stderr.write('WARNING: GAM hit Google internal bug 237397223. Please file a Google Support ticket stating that you are encountering this bug.\n')
if orderBy != 'create_time' or custom_device_filter:
controlflow.system_error_exit(5, 'GAM workaround for this issue only works if filter and orderby arguments are not used.\n')
sys.stderr.write(f' attempting to work around the bug by filtering for devices created on or after the newest we\'ve seen ({newest_device_date})...')
device_filter = f'register:{newest_device_date}..'
pageToken = None
continue
for dev in a_page.get('devices', []):
total_items += 1
devices[dev['name']] = dev
dev_date = dev.get('createTime', '')
# remove the Z
dev_date = dev_date[:-1]
# remove microseconds
dev_date = dev_date.split('.')[0]
if dev_date > newest_device_date:
newest_device_date = dev_date
pageToken = a_page.get('nextPageToken')
if not pageToken:
break
sys.stderr.write(page_message.replace('%%total_items%%', str(total_items)))
if get_device_users:
page_message = gapi.got_total_items_msg('Device Users', '...\n')
device_users = gapi.get_all_pages(ci.devices().deviceUsers(), 'list',
'deviceUsers', customer=customer, parent=parent,
page_message=page_message, pageSize=20, filter=device_filter)
for device_user in device_users:
for device in devices:
if device_user.get('name').startswith(device.get('name')):
if 'users' not in device:
device['users'] = []
device['users'].append(device_user)
break
for device in devices:
pageToken = None
newest_deviceuser_date = ''
total_items = 0
device_users = {}
if not custom_device_filter:
device_filter = None
while True:
try:
a_page = gapi.call(ci.devices().deviceUsers(),
'list',
customer=customer,
parent=parent,
pageSize=20,
orderBy=orderBy,
filter=device_filter,
pageToken=pageToken,
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O])
except googleapiclient.errors.HttpError:
sys.stderr.write('WARNING: GAM hit Google internal bug 237397223. Please file a Google Support ticket stating that you are encountering this bug.\n')
if orderBy != 'create_time' or custom_device_filter:
controlflow.system_error_exit(5, 'GAM workaround for this issue only works if filter and orderby arguments are not used.\n')
sys.stderr.write(f' attempting to work around the bug by filtering for device users created on or after the newest we\'ve seen ({newest_deviceuser_date})...')
device_filter = f'register:{newest_deviceuser_date}..'
pageToken = None
continue
for device_user in a_page.get('deviceUsers', []):
total_items += 1
dev_date = device_user.get('createTime', '')
# remove the Z
dev_date = dev_date[:-1]
# remove microseconds
dev_date = dev_date.split('.')[0]
if dev_date > newest_deviceuser_date:
newest_deviceuser_date = dev_date
deviceuser_name = device_user['name']
device_users[deviceuser_name] = device_user
pageToken = a_page.get('nextPageToken')
if not pageToken:
break
sys.stderr.write(page_message.replace('%%total_items%%', str(total_items)))
for deviceuser_name, device_user in device_users.items():
device_id = deviceuser_name.split('/')[1]
device_name = f'devices/{device_id}'
if 'users' not in devices[device_name]:
devices[device_name]['users'] = []
devices[device_name]['users'].append(device_user)
for device in devices.values():
device = utils.flatten_json(device)
for a_key in device:
if a_key not in titles:

View File

@@ -866,6 +866,13 @@ def update():
'cloudidentity.googleapis.com/groups.discussion_forum': ''
}
i += 1
elif myarg == 'dynamicsecurity':
body['labels'] = {
'cloudidentity.googleapis.com/groups.dynamic': '',
'cloudidentity.googleapis.com/groups.security': '',
'cloudidentity.googleapis.com/groups.discussion_forum': ''
}
i += 1
elif myarg in ['dynamic']:
body['dynamicGroupMetadata'] = {
'queries': [{

View File

@@ -0,0 +1,72 @@
import sys
import googleapiclient
import gam
from gam.var import * # pylint: disable=unused-wildcard-import
from gam import controlflow
from gam import display
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 orgunits as gapi_directory_orgunits
def _get_orgunit_customerid():
customer = GC_Values[GC_CUSTOMER_ID]
if customer != MY_CUSTOMER and not customer.startswith('C'):
customer = f'C{customer}'
return f'customers/{customer}'
def move_shared_drive(driveId, orgUnit):
_, orgUnitId = gapi_directory_orgunits.getOrgUnitId(orgUnit)
orgUnitId = f'orgUnits/{orgUnitId[3:]}'
name = f'orgUnits/-/memberships/shared_drive;{driveId}'
ci = gapi_cloudidentity.build('cloudidentity_beta')
body = {
'customer': _get_orgunit_customerid(),
'destinationOrgUnit': orgUnitId,
}
return gapi.call(ci.orgUnits().memberships(),
'move',
name=name,
body=body)
def printshow_orgunit_shared_drives(csvFormat):
orgunit = '/'
if csvFormat:
todrive = False
csvRows = []
titles = ['name']
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if csvFormat and myarg == 'todrive':
todrive = True
i += 1
elif myarg in ['ou', 'org', 'orgunit']:
orgunit = sys.argv[i + 1]
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
f"gam {['show', 'print'][csvFormat]} oushareddrives")
ci = gapi_cloudidentity.build('cloudidentity_beta')
_, orgUnitId = gapi_directory_orgunits.getOrgUnitId(orgunit)
parent = f'orgUnits/{orgUnitId[3:]}'
filter_ = "type == 'shared_drive'"
sds = gapi.get_all_pages(ci.orgUnits().memberships(),
'list',
'orgMemberships',
parent=parent,
customer=_get_orgunit_customerid(),
filter=filter_)
if not csvFormat:
for sd in sds:
display.print_json(sd)
print()
else:
for sd in sds:
display.add_row_titles_to_csv_file(
utils.flatten_json(sd),
csvRows, titles)
display.write_csv_file(csvRows, titles, f'OrgUnit {orgunit} Shared Drives', todrive)

View File

@@ -25,7 +25,7 @@ def _reduce_name(name):
def is_invitable_user(email):
'''return email isInvitableUser'''
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
svc = gapi_cloudidentity.build_dwd('cloudidentity')
customer = _get_customerid()
encoded_email = quote_plus(email)
name = f'{customer}/userinvitations/{encoded_email}'
@@ -35,7 +35,7 @@ def is_invitable_user(email):
def _generic_action(action):
'''generic function to call actionable APIs'''
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
svc = gapi_cloudidentity.build_dwd('cloudidentity')
customer = _get_customerid()
email = sys.argv[3].lower()
encoded_email = quote_plus(email)
@@ -55,7 +55,7 @@ def _generic_action(action):
def _generic_get(get_type):
'''generic function to call read data APIs'''
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
svc = gapi_cloudidentity.build_dwd('cloudidentity')
customer = _get_customerid()
email = sys.argv[3].lower()
encoded_email = quote_plus(email)
@@ -75,7 +75,7 @@ def bulk_is_invitable(emails):
if response.get('isInvitableUser'):
rows.append({'invitableUsers': request_id})
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
svc = gapi_cloudidentity.build_dwd('cloudidentity')
customer = _get_customerid()
todrive = False
#batch_size = 1000
@@ -139,7 +139,7 @@ USERINVITATION_STATE_CHOICES_MAP = {
def print_():
'''gam print userinvitations'''
svc = gapi_cloudidentity.build_dwd('cloudidentity_beta')
svc = gapi_cloudidentity.build_dwd('cloudidentity')
customer = _get_customerid()
todrive = False
titles = ['name', 'state', 'updateTime']

View File

@@ -0,0 +1,24 @@
import gam
from gam.var import GC_Values, GC_CUSTOMER_ID
from gam import controlflow
from gam import gapi
from gam.gapi.directory import customer as gapi_directory_customer
def build():
return gam.buildGAPIServiceObject('cloudresourcemanager',
act_as=None)
def get_org_id():
gapi_directory_customer.setTrueCustomerId()
crm = build()
query = f'directorycustomerid:{GC_Values[GC_CUSTOMER_ID]}'
orgs = gapi.get_all_pages(crm.organizations(),
'search',
'organizations',
query=query)
if len(orgs) < 1:
# return nothing and let calling API deal with it
# since caller knows what GCP role would serve best
return
return orgs[0]['name']

View File

@@ -151,12 +151,19 @@ def doUpdateCros():
elif action == 'deprovisionupgradetransfer':
action = 'deprovision'
deprovisionReason = 'upgrade_transfer'
elif action not in ['disable', 'reenable']:
elif action in ['disable', 'reenable']:
pass
elif action == 'preprovisioneddisable':
action = 'pre_provisioned_disable'
elif action == 'preprovisionedreenable':
action = 'pre_provisioned_reenable'
else:
controlflow.system_error_exit(2, f'expected action of ' \
f'deprovision_same_model_replace, ' \
f'deprovision_different_model_replace, ' \
f'deprovision_retiring_device, ' \
f'deprovision_upgrade_transfer, disable or reenable,'
f'deprovision_upgrade_transfer, disable, reenable, '\
f'pre_provisioned_disable, pre_provisioned_reenable'\
f' got {action}')
action_body = {'action': action}
if deprovisionReason:
@@ -304,6 +311,8 @@ def doGetCrosInfo():
if 'autoUpdateExpiration' in cros:
cros['autoUpdateExpiration'] = utils.formatTimestampYMD(
cros['autoUpdateExpiration'])
if 'orgUnitId' in cros:
cros['orgUnitId'] = f"id:{cros['orgUnitId']}"
_checkTPMVulnerability(cros)
for up in CROS_SCALAR_PROPERTY_PRINT_ORDER:
if up in cros:
@@ -446,7 +455,7 @@ def doPrintCrosActivity():
selectActiveTimeRanges = selectDeviceFiles = selectRecentUsers = False
listLimit = 0
delimiter = ','
orgUnitPath = None
orgUnitPath = includeChildOrgunits = None
queries = [None]
i = 3
while i < len(sys.argv):
@@ -454,8 +463,9 @@ def doPrintCrosActivity():
if myarg in ['query', 'queries']:
queries = gam.getQueries(myarg, sys.argv[i + 1])
i += 2
elif myarg == 'limittoou':
elif myarg in {'limittoou', 'crosou', 'crosouandchildren'}:
orgUnitPath = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1])
includeChildOrgunits = myarg == 'crosouandchildren'
i += 2
elif myarg == 'todrive':
todrive = True
@@ -522,8 +532,9 @@ def doPrintCrosActivity():
query=query,
customerId=GC_Values[GC_CUSTOMER_ID],
projection='FULL',
fields=fields,
orgUnitPath=orgUnitPath)
orgUnitPath=orgUnitPath,
includeChildOrgunits=includeChildOrgunits,
fields=fields)
for cros in all_cros:
row = {}
skip_attribs = ['recentUsers', 'activeTimeRanges', 'deviceFiles']
@@ -610,7 +621,7 @@ def doPrintCrosDevices():
csvRows = []
display.add_field_to_csv_file('deviceid', CROS_ARGUMENT_TO_PROPERTY_MAP,
fieldsList, fieldsTitles, titles)
projection = orderBy = sortOrder = orgUnitPath = None
projection = orderBy = sortOrder = orgUnitPath = includeChildOrgunits = None
queries = [None]
noLists = sortHeaders = False
selectedLists = {}
@@ -622,8 +633,9 @@ def doPrintCrosDevices():
if myarg in ['query', 'queries']:
queries = gam.getQueries(myarg, sys.argv[i + 1])
i += 2
elif myarg == 'limittoou':
elif myarg in {'limittoou', 'crosou', 'crosouandchildren'}:
orgUnitPath = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1])
includeChildOrgunits = myarg == 'crosouandchildren'
i += 2
elif myarg == 'todrive':
todrive = True
@@ -727,6 +739,7 @@ def doPrintCrosDevices():
customerId=GC_Values[GC_CUSTOMER_ID],
projection=projection,
orgUnitPath=orgUnitPath,
includeChildOrgunits=includeChildOrgunits,
orderBy=orderBy,
sortOrder=sortOrder,
fields=fields)
@@ -739,6 +752,8 @@ def doPrintCrosDevices():
if 'autoUpdateExpiration' in cros:
cros['autoUpdateExpiration'] = utils.formatTimestampYMD(
cros['autoUpdateExpiration'])
if 'orgUnitId' in cros:
cros['orgUnitId'] = f"id:{cros['orgUnitId']}"
for cpuStatusReport in cros.get('cpuStatusReports', []):
tempInfos = cpuStatusReport.get('cpuTemperatureInfo', [])
for tempInfo in tempInfos:
@@ -753,6 +768,8 @@ def doPrintCrosDevices():
if 'autoUpdateExpiration' in cros:
cros['autoUpdateExpiration'] = utils.formatTimestampYMD(
cros['autoUpdateExpiration'])
if 'orgUnitId' in cros:
cros['orgUnitId'] = f"id:{cros['orgUnitId']}"
row = {}
for attrib in cros:
if attrib not in {

View File

@@ -95,12 +95,10 @@ def doGetCustomerInfo():
continue
except gapi.errors.GapiForbiddenError:
return
warnings = result.get('warnings', [])
fullDataRequired = ['accounts']
usage = result.get('usageReports')
has_reports = bool(usage)
fullData, tryDate = gapi_reports._check_full_data_available(
warnings, tryDate, fullDataRequired, has_reports)
result, tryDate, fullDataRequired, False)
if fullData < 0:
print('No user report available.')
sys.exit(1)

View File

@@ -160,42 +160,15 @@ def info(name=None, return_attrib=None):
print('')
def print_():
print_order = [
'orgUnitPath', 'orgUnitId', 'name', 'description', 'parentOrgUnitPath',
'parentOrgUnitId', 'blockInheritance'
]
cd = gapi_directory.build()
listType = 'all'
orgUnitPath = '/'
todrive = False
fields = ['orgUnitPath', 'name', 'orgUnitId', 'parentOrgUnitId']
titles = []
csvRows = []
parentOrgIds = []
def list_orgunits(listType='all', orgUnitPath=None, fields=None):
retrievedOrgIds = []
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'todrive':
todrive = True
i += 1
elif myarg == 'toplevelonly':
listType = 'children'
i += 1
elif myarg == 'fromparent':
orgUnitPath = getOrgUnitItem(sys.argv[i + 1])
i += 2
elif myarg == 'allfields':
fields = None
i += 1
elif myarg == 'fields':
fields += sys.argv[i + 1].split(',')
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam print orgs')
gam.printGettingAllItems('Organizational Units', None)
parentOrgIds = []
cd = gapi_directory.build()
if fields:
# Always get parentOrgUnitId so we can
# find missing parents
if 'parentOrgUnitId' not in fields:
fields.append('parentOrgUnitId')
get_fields = ','.join(fields)
list_fields = f'organizationUnits({get_fields})'
else:
@@ -230,6 +203,44 @@ def print_():
orgunits.append(result)
except:
pass
return orgunits
def print_():
print_order = [
'orgUnitPath', 'orgUnitId', 'name', 'description', 'parentOrgUnitPath',
'parentOrgUnitId', 'blockInheritance'
]
listType = 'all'
orgUnitPath = '/'
todrive = False
fields = ['orgUnitPath', 'name', 'orgUnitId', 'parentOrgUnitId']
titles = []
csvRows = []
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'todrive':
todrive = True
i += 1
elif myarg == 'toplevelonly':
listType = 'children'
i += 1
elif myarg == 'fromparent':
orgUnitPath = getOrgUnitItem(sys.argv[i + 1])
i += 2
elif myarg == 'allfields':
fields = None
i += 1
elif myarg == 'fields':
fields += sys.argv[i + 1].split(',')
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam print orgs')
gam.printGettingAllItems('Organizational Units', None)
orgunits = list_orgunits(listType=listType,
orgUnitPath=orgUnitPath,
fields=fields)
for row in orgunits:
orgEntity = {}
for key, value in list(row.items()):
@@ -248,6 +259,11 @@ def print_():
display.write_csv_file(csvRows, titles, 'Orgs', todrive)
def orgid_to_org_map():
orgunits = list_orgunits(fields=['orgUnitPath', 'orgUnitId'])
result = {ou['orgUnitId']:ou['orgUnitPath'] for ou in orgunits}
return result
def update():
cd = gapi_directory.build()
orgUnitPath = getOrgUnitItem(sys.argv[3])
@@ -357,17 +373,20 @@ def makeOrgUnitPathRelative(path):
def encodeOrgUnitPath(path):
if path.find('+') == -1 and path.find('%') == -1:
return path
encpath = ''
for c in path:
if c == '+':
encpath += '%2B'
elif c == '%':
encpath += '%25'
else:
encpath += c
return encpath
# 6.22 - This method no longer works.
# % no longer needs encoding and + is handled incorrectly in API with or without encoding
return path
# if path.find('+') == -1 and path.find('%') == -1:
# return path
# encpath = ''
# for c in path:
# if c == '+':
# encpath += '%2B'
# elif c == '%':
# encpath += '%25'
# else:
# encpath += c
# return encpath
def getOrgUnitItem(orgUnit, pathOnly=False, absolutePath=True):

View File

@@ -227,6 +227,22 @@ def printFeatures():
display.write_csv_file(csvRows, titles, 'Features', to_drive)
BUILDING_ADDRESS_FIELD_MAP = {
'address': 'addressLines',
'addresslines': 'addressLines',
'administrativearea': 'administrativeArea',
'city': 'locality',
'country': 'regionCode',
'language': 'languageCode',
'languagecode': 'languageCode',
'locality': 'locality',
'postalcode': 'postalCode',
'regioncode': 'regionCode',
'state': 'administrativeArea',
'sublocality': 'sublocality',
'zipcode': 'postalCode',
}
def _getBuildingAttributes(args, body={}):
i = 0
while i < len(args):
@@ -253,6 +269,16 @@ def _getBuildingAttributes(args, body={}):
elif myarg == 'floors':
body['floorNames'] = args[i + 1].split(',')
i += 2
elif myarg in BUILDING_ADDRESS_FIELD_MAP:
myarg = BUILDING_ADDRESS_FIELD_MAP[myarg]
body.setdefault('address', {})
if myarg == 'addressLines':
body['address'][myarg] = args[i + 1].split('\n')
elif myarg == 'languageCode':
body['address'][myarg] = LANGUAGE_CODES_MAP.get(args[i + 1].lower(), args[i + 1])
else:
body['address'][myarg] = args[i + 1]
i += 2
else:
controlflow.invalid_argument_exit(myarg,
'gam create|update building')

View File

@@ -21,6 +21,18 @@ def create():
body['roleId'] = gapi_directory_roles.getRoleId(role)
body['scopeType'] = sys.argv[5].upper()
i = 6
if body['scopeType'] not in ['CUSTOMER', 'ORG_UNIT']:
controlflow.expected_argument_exit('scope type',
', '.join(['customer', 'org_unit']),
body['scopeType'])
if body['scopeType'] == 'ORG_UNIT':
orgUnit, orgUnitId = gapi_directory_orgunits.getOrgUnitId(
sys.argv[i], cd)
body['orgUnitId'] = orgUnitId[3:]
scope = f'ORG_UNIT {orgUnit}'
i = 7
else:
scope = 'CUSTOMER'
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'condition':
@@ -33,17 +45,6 @@ def create():
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam create admin')
if body['scopeType'] not in ['CUSTOMER', 'ORG_UNIT']:
controlflow.expected_argument_exit('scope type',
', '.join(['customer', 'org_unit']),
body['scopeType'])
if body['scopeType'] == 'ORG_UNIT':
orgUnit, orgUnitId = gapi_directory_orgunits.getOrgUnitId(
sys.argv[6], cd)
body['orgUnitId'] = orgUnitId[3:]
scope = f'ORG_UNIT {orgUnit}'
else:
scope = 'CUSTOMER'
print(f'Giving {user} admin role {role} for {scope}')
gapi.call(cd.roleAssignments(),
'insert',

View File

@@ -58,22 +58,32 @@ def getRoleId(role):
def getPrivileges(body, privs, action):
all_privileges = gapi_directory_privileges.print_(return_only=True)
def expandChildPrivileges(privilege):
for childPrivilege in privilege.get('childPrivileges', []):
childPrivileges[childPrivilege['privilegeName']] = childPrivilege['serviceId']
expandChildPrivileges(childPrivilege)
allPrivileges = {}
ouPrivileges = {}
childPrivileges = {}
for privilege in gapi_directory_privileges.print_(return_only=True):
allPrivileges[privilege['privilegeName']] = privilege['serviceId']
if privilege['isOuScopable']:
ouPrivileges[privilege['privilegeName']] = privilege['serviceId']
expandChildPrivileges(privilege)
if privs == 'ALL':
body['rolePrivileges'] = [
{'privilegeName': p['privilegeName'], 'serviceId': p['serviceId']} for p in all_privileges
]
body['rolePrivileges'] = [{'privilegeName': priv, 'serviceId': v} for priv, v in allPrivileges.items()]
elif privs == 'ALL_OU':
body['rolePrivileges'] = [
{'privilegeName': p['privilegeName'], 'serviceId': p['serviceId']} for p in all_privileges if p.get('isOuScopable')
]
body['rolePrivileges'] = [{'privilegeName': priv, 'serviceId': v} for priv, v in ouPrivileges.items()]
else:
body.setdefault('rolePrivileges', [])
for priv in privs.split(','):
for p in all_privileges:
if priv == p['privilegeName']:
body['rolePrivileges'].append({'privilegeName': p['privilegeName'], 'serviceId': p['serviceId']})
break
if priv in allPrivileges:
body['rolePrivileges'].append({'privilegeName': priv, 'serviceId': allPrivileges[priv]})
elif priv in ouPrivileges:
body['rolePrivileges'].append({'privilegeName': priv, 'serviceId': ouPrivileges[priv]})
elif priv in childPrivileges:
body['rolePrivileges'].append({'privilegeName': priv, 'serviceId': childPrivileges[priv]})
else:
controlflow.invalid_argument_exit(priv,
f'gam {action} adminrole privileges')

View File

@@ -0,0 +1,8 @@
import gam
def build(user=None):
if not user:
user = gam._get_admin_email()
userEmail = gam.convertUIDtoEmailAddress(user)
return (userEmail, gam.buildGAPIServiceObject('drive3', userEmail))

View File

@@ -0,0 +1,26 @@
"""Methods related to Drive API Shared Drives"""
import sys
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 drive as gapi_drive
def drive_name_to_id(name, drive=None):
if not drive:
_, drive = gapi_drive.build()
q = f"name = '{name}'"
sds = gapi.get_all_pages(drive.drives(),
'list',
'drives',
q=q,
useDomainAdminAccess=True)
if len(sds) == 0:
controlflow.system_error_exit(3, f'Could not find shared drive named "{name}"')
elif len(sds) > 1:
controlflow.system_error_exit(3, f'Got more than one shared drive named "{name}"')
return sds[0]['id']

View File

@@ -120,6 +120,7 @@ class ErrorReason(Enum):
FAILED_PRECONDITION = 'failedPrecondition'
FORBIDDEN = 'forbidden'
FIVE_O_THREE = '503'
FIVE_O_O = '500'
FOUR_O_NINE = '409'
FOUR_O_O = '400'
FOUR_O_FOUR = '404'
@@ -159,6 +160,7 @@ DEFAULT_RETRY_REASONS = [
ErrorReason.GATEWAY_TIMEOUT,
ErrorReason.INTERNAL_ERROR,
ErrorReason.FOUR_TWO_NINE,
ErrorReason.FIVE_O_O,
ErrorReason.FIVE_O_THREE,
]
GMAIL_THROW_REASONS = [ErrorReason.SERVICE_NOT_AVAILABLE]
@@ -231,6 +233,7 @@ ERROR_REASON_TO_EXCEPTION = {
OAUTH2_TOKEN_ERRORS = [
'access_denied',
'access_denied: Requested client not authorized',
'access_denied: Account restricted',
'internal_failure: Backend Error',
'internal_failure: None',
'invalid_grant',

View File

@@ -56,7 +56,7 @@ def create(users, sku=None):
productId = sys.argv[i+1]
i += 2
for user in users:
print(f'Adding license {sku_name} from to {user}')
print(f'Adding license {sku_name} to {user}')
gapi.call(lic.licenseAssignments(),
'insert',
soft_errors=True,

View File

@@ -85,15 +85,13 @@ def showUsageParameters():
customerId=customerId,
fields='warnings,usageReports(parameters(name))',
**kwargs)
warnings = result.get('warnings', [])
usage = result.get('usageReports')
has_reports = bool(usage)
fullData, tryDate = _check_full_data_available(
warnings, tryDate, fullDataRequired, has_reports)
result, tryDate, fullDataRequired, False)
if fullData < 0:
print('No usage parameters available.')
sys.exit(1)
if has_reports:
if usage:
for parameter in usage[0]['parameters']:
name = parameter.get('name')
if name:
@@ -350,10 +348,8 @@ def showReport():
orgUnitID=orgUnitId,
fields='warnings,usageReports',
maxResults=1)
warnings = one_page.get('warnings', [])
has_reports = bool(one_page.get('usageReports'))
fullData, tryDate = _check_full_data_available(
warnings, tryDate, fullDataRequired, has_reports)
one_page, tryDate, fullDataRequired, True)
if fullData < 0:
print('No user report available.')
sys.exit(1)
@@ -382,7 +378,7 @@ def showReport():
for user_report in usage:
if 'entity' not in user_report:
continue
row = {'email': user_report['entity']['userEmail'], 'date': tryDate}
row = {'email': user_report['entity'].get('userEmail', 'Unknown'), 'date': tryDate}
for item in user_report.get('parameters', []):
if 'name' not in item:
continue
@@ -407,10 +403,8 @@ def showReport():
customerId=customerId,
date=tryDate,
fields='warnings,usageReports')
warnings = first_page.get('warnings', [])
has_reports = bool(first_page.get('usageReports'))
fullData, tryDate = _check_full_data_available(
warnings, tryDate, fullDataRequired, has_reports)
first_page, tryDate, fullDataRequired, False)
if fullData < 0:
print('No customer report available.')
sys.exit(1)
@@ -563,15 +557,16 @@ def _adjust_date(errMsg):
return str(match_date.group(1))
def _check_full_data_available(warnings, tryDate, fullDataRequired,
has_reports):
def _check_full_data_available(result, tryDate, fullDataRequired,
checkUserEmail):
one_day = datetime.timedelta(days=1)
tryDateTime = datetime.datetime.strptime(tryDate, YYYYMMDD_FORMAT)
# move to day before if we don't have at least one usageReport
if not has_reports:
usage = result.get('usageReports')
if not usage:
tryDateTime -= one_day
return (0, tryDateTime.strftime(YYYYMMDD_FORMAT))
for warning in warnings:
for warning in result.get('warnings', []):
if warning['code'] == 'PARTIAL_DATA_AVAILABLE':
for app in warning['data']:
if app['key'] == 'application' and \
@@ -586,4 +581,8 @@ def _check_full_data_available(warnings, tryDate, fullDataRequired,
app['value'] != 'docs' and \
(not fullDataRequired or app['value'] in fullDataRequired):
return (-1, tryDate)
if checkUserEmail:
if 'entity' not in usage[0] or 'userEmail' not in usage[0]['entity']:
tryDateTime -= one_day
return (0, tryDateTime.strftime(YYYYMMDD_FORMAT))
return (1, tryDate)

View File

@@ -348,7 +348,7 @@ def print_count():
# 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', [])
query_accounts = query.get('accountInfo', {}).get('emails', [])
elif search_method == 'ENTIRE_ORG':
query_accounts = gam.getUsersToModify('all', 'users')
elif search_method == 'ORG_UNIT':
@@ -367,8 +367,9 @@ def print_count():
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)
if email:
csv_rows.append({'account': email, 'count': account.get('count', 0)})
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']

View File

@@ -8,10 +8,10 @@ import platform
import re
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
GAM_VERSION = '6.16'
GAM_VERSION = '6.23'
GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
GAM_URL = 'https://git.io/gam'
GAM_URL = 'https://jaylee.us/gam'
GAM_INFO = (
f'GAM {GAM_VERSION} - {GAM_URL} / {GAM_AUTHOR} / '
f'Python {platform.python_version()} {sys.version_info.releaselevel} / '
@@ -31,7 +31,8 @@ usergroup_types = [
'ou_and_child', 'ou_and_children_ns', 'ou_and_child_ns',
'ou_and_children_susp', 'ou_and_child_susp', 'query', 'queries', 'license',
'licenses', 'licence', 'licences', 'file', 'csv', 'csvfile', 'all', 'cros',
'cros_sn', 'crosquery', 'crosqueries', 'crosfile', 'croscsv', 'croscsvfile'
'cros_sn', 'crosquery', 'crosqueries', 'crosfile', 'croscsv', 'croscsvfile',
'cros_ou', 'cros_ou_and_children'
]
ERROR_PREFIX = 'ERROR: '
WARNING_PREFIX = 'WARNING: '
@@ -42,7 +43,7 @@ FN_EXTRA_ARGS_TXT = 'extra-args.txt'
FN_LAST_UPDATE_CHECK_TXT = 'lastupdatecheck.txt'
MY_CUSTOMER = 'my_customer'
# See https://support.google.com/drive/answer/37603
MAX_GOOGLE_SHEET_CELLS = 5000000
MAX_GOOGLE_SHEET_CELLS = 10000000
MAX_LOCAL_GOOGLE_TIME_OFFSET = 30
SKUS = {
@@ -116,6 +117,11 @@ SKUS = {
'aliases': ['gvpremier', 'voicepremier', 'googlevoicepremier'],
'displayName': 'Google Voice Premier'
},
'1010360001': {
'product': '101036',
'aliases': ['meetdialing','googlemeetglobaldialing'],
'displayName': 'Google Meet Global Dialing'
},
'1010370001': {
'product': '101037',
'aliases': ['gwetlu', 'workspaceeducationupgrade'],
@@ -193,6 +199,11 @@ SKUS = {
'wsentplus', 'workspaceenterpriseplus'],
'displayName': 'Workspace Enterprise Plus'
},
'1010020029': {
'product': 'Google-Apps',
'aliases': ['wes', 'workspaceenterprisestarter'],
'displayName': 'Workspace Enterprise Starter'
},
'1010020030': {
'product': 'Google-Apps',
'aliases': ['workspacefrontline', 'workspacefrontlineworker'],
@@ -277,6 +288,7 @@ PRODUCTID_NAME_MAPPINGS = {
'101033': 'Google Voice',
'101034': 'G Suite Archived',
'101035': 'Cloud Search',
'101036': 'Google Meet Global Dialing',
'101037': 'G Suite Workspace for Education',
'Google-Apps': 'Google Workspace',
'Google-Chrome-Device-Management': 'Google Chrome Device Management',
@@ -301,6 +313,7 @@ API_NAME_MAPPING = {
}
API_VER_MAPPING = {
'accesscontextmanager': 'v1',
'alertcenter': 'v1beta1',
'driveactivity': 'v2',
'calendar': 'v3',
@@ -976,6 +989,7 @@ CROS_ARGUMENT_TO_PROPERTY_MAP = {
'notes': ['notes',],
'ordernumber': ['orderNumber',],
'org': ['orgUnitPath',],
'orgunitid': ['orgUnitId',],
'orgunitpath': ['orgUnitPath',],
'osversion': ['osVersion',],
'ou': ['orgUnitPath',],
@@ -1001,6 +1015,7 @@ CROS_BASIC_FIELDS_LIST = [
]
CROS_SCALAR_PROPERTY_PRINT_ORDER = [
'orgUnitId',
'orgUnitPath',
'annotatedAssetId',
'annotatedLocation',
@@ -1524,6 +1539,9 @@ MESSAGE_UPDATE_GAM_TO_64BIT = 'You\'re running a 32-bit version of GAM on a' \
MESSAGE_YOUR_SYSTEM_TIME_DIFFERS_FROM_GOOGLE_BY = 'Your system time differs' \
' from %s by %s'
shared_drive_values = ['teamdrive', 'teamdrives',
'shareddrive', 'shareddrives']
USER_ADDRESS_TYPES = ['home', 'work', 'other']
USER_EMAIL_TYPES = ['home', 'work', 'other']
USER_EXTERNALID_TYPES = [

View File

@@ -1,3 +1,4 @@
accesscontextmanager.googleapis.com
admin.googleapis.com
alertcenter.googleapis.com
calendar-json.googleapis.com
@@ -6,6 +7,7 @@ chromemanagement.googleapis.com
chromepolicy.googleapis.com
classroom.googleapis.com
cloudidentity.googleapis.com
cloudresourcemanager.googleapis.com
contacts.googleapis.com
drive.googleapis.com
driveactivity.googleapis.com

View File

@@ -1,6 +1,6 @@
[metadata]
name = GAM for Google Workspace
version = 6.0.14
version = 6.0.23
description = Command line management for Google Workspaces
long_description = file: readme.md
long_description_content_type = text/markdown