Compare commits

...

330 Commits
v6.13 ... v6.22

Author SHA1 Message Date
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
Jay Lee
50f2040eb2 disable user undelete for now 2022-02-27 19:05:28 +00:00
Jay Lee
b76a8f7d76 only look for open matters 2022-02-27 18:58:29 +00:00
Jay Lee
9b0ab6b1c1 support new remove_reset_lock device wipe capability 2022-02-27 18:41:53 +00:00
Jay Lee
bf899f5044 support new export method for Vault 2022-02-27 18:31:56 +00:00
Jay Lee
75a20b66d3 Explain URL copy to users 2022-02-27 17:11:49 +00:00
Ross Scroggs
018862d012 Allow user copy/paste retries in gam oauth create (#1488) 2022-02-27 11:46:41 -05:00
Ross Scroggs
87d3714ed0 Cleanup (#1486)
* Cleanup

* Update oauth.py
2022-02-26 16:12:19 -05:00
Jay Lee
f8f5ee7f25 Update build.yml 2022-02-23 12:28:19 -05:00
Jay Lee
45e772acde Update oauth_test.py 2022-02-18 17:59:52 -05:00
Jay Lee
0b7a79bce0 rough draft work to move GAM off oob auth #1483 2022-02-18 16:06:40 -05:00
ejochman
4a9d571cb1 Update LICENSE file contents and location (#1482)
Move LICENSE to root and remove non-applicable "server subcomponents"
section.
2022-02-15 10:44:19 -05:00
Ross Scroggs
1d5a8ec81b Add verifytarget to update alias to address Issue #1479 (#1481)
* Add verifytarget to update alias to address Issue #1479

* Make default behavior in update alias to be verify; provide opt-out

* Update __init__.py

* Remove verifynotinvitable check on update alias
2022-02-12 10:59:27 -05:00
Ross Scroggs
f6c4e26b3b Document create admin condition (#1476)
* Document create admin condition

* Print condition  in original form
2022-02-11 20:43:50 -05:00
Jay Lee
536fded762 Update gam-install.sh 2022-02-06 08:54:23 -05:00
Jay Lee
4a79b3f42c Update var.py 2022-02-04 09:52:38 -05:00
Jay Lee
6e5052f6ab Update build.yml 2022-02-04 09:21:21 -05:00
Jay Lee
fddaeca050 Merge branch 'main' of https://github.com/GAM-team/GAM 2022-02-03 19:13:06 +00:00
Jay Lee
5b53ba33ab add build_beta 2022-02-03 19:12:29 +00:00
Jay Lee
583fb8d6d2 Update build.yml 2022-02-03 14:10:13 -05:00
Jay Lee
9fa51836c7 remove print debug statement 2022-02-03 19:03:50 +00:00
Jay Lee
6432dd1fef Update build.yml 2022-02-03 13:57:51 -05:00
Jay Lee
341d61444c Allow conditions for admin role assignments 2022-02-03 18:45:35 +00:00
Ross Scroggs
40f71cc703 Update setup.cfg (#1474) 2022-02-02 15:02:38 -05:00
Jay Lee
a8155b9a39 Update gam-install.sh 2022-02-02 14:22:45 -05:00
Jay Lee
f5e9aea2ac [no ci] remove old file 2022-02-02 10:39:10 -05:00
Jay Lee
be41f0ba11 [no ci] remove old file 2022-02-02 10:39:02 -05:00
Jay Lee
f92ca02907 [no ci] remove old file 2022-02-02 10:38:54 -05:00
Jay Lee
7ccddd3d33 [no ci] remove old file 2022-02-02 10:38:41 -05:00
Jay Lee
545c4ec30f [no ci] remove old file 2022-02-02 10:38:32 -05:00
Jay Lee
7aa58c8287 [no ci] update wheel 2022-02-02 10:38:07 -05:00
Jay Lee
32bd11dccc Update build.yml 2022-02-02 10:04:31 -05:00
Jay Lee
1c6488312e [no ci] 7z.exe verbose output 2022-02-02 10:00:32 -05:00
Jay Lee
d767e33da5 [no ci] list installed pip packages and versions 2022-02-02 09:52:01 -05:00
Jay Lee
cee82e7408 [no ci] remove debug lines 2022-02-02 09:33:19 -05:00
Jay Lee
8361a47259 rollback cache since setuptools was issue 2022-02-02 09:15:49 -05:00
Jay Lee
1032421fdd Update build.yml 2022-02-02 08:41:41 -05:00
Jay Lee
17e52e2598 Update build.yml 2022-02-02 08:16:12 -05:00
Jay Lee
4265f86c48 Update resource.py 2022-02-02 07:40:22 -05:00
Jay Lee
f82535c497 Update build.yml 2022-02-01 13:43:43 -05:00
Jay Lee
d25b80ee9c Update build.yml 2022-02-01 13:39:25 -05:00
Jay Lee
0c019d07a2 Update build.yml 2022-02-01 13:08:51 -05:00
Jay Lee
dcaa9c56c0 Update build.yml 2022-02-01 13:02:32 -05:00
Jay Lee
87d45840d9 Update build.yml 2022-02-01 12:57:42 -05:00
Jay Lee
8174aae392 Create package_exclusions.txt 2022-02-01 12:49:20 -05:00
Jay Lee
2a916d1d45 Update build.yml 2022-02-01 09:11:34 -05:00
Jay Lee
b628c34b20 Update build.yml 2022-02-01 09:08:07 -05:00
Jay Lee
f161b165b2 Update build.yml 2022-02-01 09:02:32 -05:00
Jay Lee
316ee693e3 Update build.yml 2022-02-01 08:47:12 -05:00
Jay Lee
82bfb99548 Update build.yml 2022-01-31 10:13:10 -05:00
Jay Lee
ed5ccf1faa Update build.yml 2022-01-31 10:08:54 -05:00
Jay Lee
fd3bec8371 Delete windows-install.sh 2022-01-31 10:05:53 -05:00
Jay Lee
9e2bf9cbbe Update build.yml 2022-01-31 08:08:22 -05:00
Jay Lee
ec198e818a Update build.yml 2022-01-26 21:45:59 -05:00
Jay Lee
aeeba5c668 Update build.yml 2022-01-26 20:28:52 -05:00
Jay Lee
0ca5c74ce7 Update build.yml 2022-01-26 20:28:21 -05:00
Jay Lee
b6c1ad5ce7 Update build.yml 2022-01-26 18:51:07 -05:00
Jay Lee
4c64671cb8 Update build.yml 2022-01-26 17:55:56 -05:00
Jay Lee
7773c49112 Update build.yml 2022-01-26 17:08:49 -05:00
Jay Lee
5e451e4fe3 Update build.yml 2022-01-26 16:21:06 -05:00
Jay Lee
0801ef66a0 Update build.yml 2022-01-26 15:07:33 -05:00
Jay Lee
31f0064e53 Update build.yml 2022-01-26 14:24:01 -05:00
Jay Lee
142191caeb Update build.yml 2022-01-26 14:21:23 -05:00
Jay Lee
d5ee92f12a Update build.yml 2022-01-26 13:46:16 -05:00
Jay Lee
2cd36e6244 Update build.yml 2022-01-26 13:38:52 -05:00
Jay Lee
7b4b637ec3 Update build.yml 2022-01-26 13:29:00 -05:00
Jay Lee
ca931d1caa Update build.yml 2022-01-26 13:18:41 -05:00
Jay Lee
e6773a09c0 Update build.yml 2022-01-26 12:34:18 -05:00
Jay Lee
006b0f1c9d Update build.yml 2022-01-25 21:31:38 -05:00
Jay Lee
14c87f427e Update build.yml 2022-01-25 20:11:50 -05:00
Jay Lee
310cc87a5e Update build.yml 2022-01-25 19:25:15 -05:00
Jay Lee
7533eb0540 Update build.yml 2022-01-25 18:45:27 -05:00
Jay Lee
e5d9a84fc8 Update build.yml 2022-01-25 18:23:05 -05:00
Jay Lee
fb2bb0cb09 Update build.yml 2022-01-25 18:00:01 -05:00
Jay Lee
4a7ce7ebfb Update build.yml 2022-01-25 17:16:52 -05:00
Jay Lee
8eaaabe5da Update build.yml 2022-01-25 16:59:13 -05:00
Jay Lee
f78bdf834d [no ci] verbose cp 2022-01-25 16:40:02 -05:00
Jay Lee
86cc187eed [no ci] verbose mkdir 2022-01-25 16:37:52 -05:00
Jay Lee
9ae4ee1430 Update build.yml 2022-01-25 16:35:02 -05:00
Jay Lee
2a71a0f0be Update build.yml 2022-01-25 15:55:28 -05:00
Jay Lee
b3cf0c1bde Update build.yml 2022-01-25 14:46:33 -05:00
Jay Lee
b4e01737c7 Update build.yml 2022-01-25 14:25:54 -05:00
Jay Lee
8243fe8846 Update build.yml 2022-01-25 14:04:24 -05:00
Jay Lee
44b9a3ca8a Update build.yml 2022-01-25 13:31:08 -05:00
Jay Lee
52becd0255 Update build.yml 2022-01-25 13:29:07 -05:00
Jay Lee
4774227a76 Update build.yml 2022-01-25 13:27:41 -05:00
Jay Lee
faaeeb5f72 Update build.yml 2022-01-25 13:23:35 -05:00
Jay Lee
499eb45064 Update build.yml 2022-01-25 12:50:46 -05:00
Jay Lee
86d2dc725b Update build.yml 2022-01-25 12:47:25 -05:00
Jay Lee
39104183e9 Update build.yml 2022-01-25 12:45:33 -05:00
Jay Lee
ce41283a71 Update build.yml 2022-01-25 12:02:54 -05:00
Jay Lee
ad8aaa1738 Create openssl.props 2022-01-25 12:02:15 -05:00
Jay Lee
7ff381cacf Update build.yml 2022-01-25 11:15:52 -05:00
Jay Lee
e522f76db6 Update build.yml 2022-01-25 11:08:35 -05:00
Jay Lee
106e3544a8 Update build.yml 2022-01-25 10:55:42 -05:00
Jay Lee
704fd3bea8 Update build.yml 2022-01-25 10:52:58 -05:00
Jay Lee
c8f13eedbc Update build.yml 2022-01-25 10:46:59 -05:00
Jay Lee
959cf3d483 Update build.yml 2022-01-25 10:30:00 -05:00
Jay Lee
7d59ceb9d1 Update build.yml 2022-01-25 10:21:40 -05:00
Jay Lee
cdcb071826 Update build.yml 2022-01-25 10:17:17 -05:00
Jay Lee
2829c4c26a Update build.yml 2022-01-25 10:13:45 -05:00
Jay Lee
c810874014 Update build.yml 2022-01-25 10:09:54 -05:00
Jay Lee
e1d9aef2d7 Update gam-install.sh 2022-01-24 10:17:37 -05:00
Jay Lee
71e526370c Delete versionhistory-v1.json 2022-01-23 11:12:03 -05:00
Jay Lee
3c7df4974c [no ci] vh discovery doc is now public, remove local 2022-01-23 11:11:44 -05:00
Janosh Riebesell
f5c95d2ba0 Flynt + shellcheck (#1473)
* quote variables in src/gam-install.sh + fix typos

* flynt src (auto f-string conversion)

* quote all shell variables
2022-01-23 11:08:23 -05:00
Jay Lee
a4f09c02e8 Update gam-install.sh 2022-01-21 14:37:08 -05:00
Jay Lee
be8363786c Update build.yml 2022-01-17 16:34:08 -05:00
Janosh Riebesell
7710711def Pre-commit (#1467)
* pre-commit run trailing-whitespace -a

* pre-commit run end-of-file-fixer -a

* pre-commit run double-quote-string-fixer -a

* pre-commit run requirements-txt-fixer -a

* add pyupgrade hook

* remove pre-commit default_language_version 3.7 (since no upwards incompatible hooks)
2022-01-16 14:23:27 -05:00
Jay Lee
7320136079 Update build.yml 2022-01-15 16:19:00 -05:00
Jay Lee
eaced6942a Update var.py 2022-01-15 14:18:04 -05:00
Ross Scroggs
5411724696 Improve gam user show vaultholds (#1472)
* Improve gam user show vaultholds

* Handle user in /

* Handle user in / more efficiently

* Display total holds, don't set rc, allow uid
2022-01-15 12:24:08 -05:00
Jay Lee
ef3c282bfa Update linux-before-install.sh 2022-01-15 12:23:56 -05:00
Jay Lee
1a1cd223d3 Update linux-before-install.sh 2022-01-15 11:54:18 -05:00
Jay Lee
3d55591436 Update linux-before-install.sh 2022-01-15 11:42:52 -05:00
Jay Lee
b5b9cfe2aa Update build.yml 2022-01-15 11:22:31 -05:00
Ross Scroggs
39db76f189 Four changes (#1471)
* Three changes

Document:
gam <UserTypeEntity> show vaultholds|holds
hidden <Boolean> for update teamsrive

Fix showHoldsForUsers

* Standardize update teamdrive input of orgUnitId

* One too many =
2022-01-15 04:01:09 -05:00
Jay Lee
303e32fe5d Update build.yml 2022-01-14 20:18:50 -05:00
Jay Lee
dd4841c82c Update build.yml 2022-01-14 19:58:28 -05:00
Jay Lee
add970c0ae Update var.py 2022-01-14 15:33:43 -05:00
Jay Lee
7df3e70722 Update README.md 2022-01-14 15:32:25 -05:00
Jay Lee
ea4459b89e Update gam-install.sh 2022-01-14 15:31:06 -05:00
Jay Lee
259c952636 Better error on user delete pre-condition. 2022-01-14 13:50:10 -05:00
Jay Lee
15b1ce370c Show holds for users, hide shared drives 2022-01-14 13:35:06 -05:00
Ross Scroggs
f7ab4aef4e Handle missing descriptions in Chrome polilcies (#1468)
Document Chrome Browser field
2022-01-12 18:00:01 -05:00
55 changed files with 10052 additions and 2038 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

@@ -1,103 +0,0 @@
echo "RUNNING: apt update..."
sudo apt-get -qq --yes update > /dev/null
sudo apt-get -qq --yes install swig libpcsclite-dev
if [[ "$TRAVIS_JOB_NAME" == *"Testing" ]]; then
export python="python"
export pip="pip"
echo "Travis setup Python $TRAVIS_PYTHON_VERSION"
echo "running tests with this version"
else
export whereibelong=$(pwd)
echo "We are running on $ImageOS $ImageVersion"
export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
cpucount=$(nproc --all)
echo "This device has $cpucount CPUs for compiling..."
SSLVER=$(~/ssl/bin/openssl version)
SSLRESULT=$?
PYVER=$(~/python/bin/python3 -V)
PYRESULT=$?
if [ $SSLRESULT -ne 0 ] || [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]] || [ $PYRESULT -ne 0 ] || [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION"* ]]; then
echo "SSL Result: $SSLRESULT - SSL Ver: $SSLVER - Py Result: $PYRESULT - Py Ver: $PYVER"
if [ $SSLRESULT -ne 0 ]; then
echo "sslresult -ne 0"
fi
if [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]]; then
echo "sslver not equal to..."
fi
if [ $PYRESULT -ne 0 ]; then
echo "pyresult -ne 0"
fi
if [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION" ]]; then
echo "pyver not equal to..."
fi
cd ~
rm -rf ssl
rm -rf python
mkdir ssl
mkdir python
echo "RUNNING: apt upgrade..."
sudo apt-mark hold openssh-server
sudo apt-get --yes upgrade
sudo apt-get --yes --with-new-pkgs upgrade
echo "Installing build tools..."
sudo apt-get -qq --yes install build-essential
echo "Installing deps for python3"
sudo cp -v /etc/apt/sources.list /tmp
sudo chmod a+rwx /tmp/sources.list
echo "deb-src http://archive.ubuntu.com/ubuntu/ $TRAVIS_DIST main" >> /tmp/sources.list
sudo cp -v /tmp/sources.list /etc/apt
sudo apt-get -qq --yes update > /dev/null
sudo apt-get -qq --yes build-dep python3 > /dev/null
# Compile latest OpenSSL
wget --quiet https://www.openssl.org/source/openssl-$BUILD_OPENSSL_VERSION.tar.gz
echo "Extracting OpenSSL..."
tar xf openssl-$BUILD_OPENSSL_VERSION.tar.gz
cd openssl-$BUILD_OPENSSL_VERSION
echo "Compiling OpenSSL $BUILD_OPENSSL_VERSION..."
./Configure --libdir=lib --prefix=$HOME/ssl
echo "Running make for OpenSSL..."
make -j$cpucount -s
echo "Running make install for OpenSSL..."
make install > /dev/null
cd ~
# Compile latest Python
echo "Downloading Python $BUILD_PYTHON_VERSION..."
curl -O https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/Python-$BUILD_PYTHON_VERSION.tar.xz
echo "Extracting Python..."
tar xf Python-$BUILD_PYTHON_VERSION.tar.xz
cd Python-$BUILD_PYTHON_VERSION
echo "Compiling Python $BUILD_PYTHON_VERSION..."
safe_flags="--with-openssl=$HOME/ssl --enable-shared --prefix=$HOME/python --with-ensurepip=upgrade"
unsafe_flags="--enable-optimizations --with-lto --with-openssl=~/ssl --with-openssl-rpath=~~/ssl/lib"
if [ ! -e Makefile ]; then
echo "running configure with safe and unsafe"
./configure $safe_flags $unsafe_flags > /dev/null
fi
#make -j$cpucount PROFILE_TASK="-m test.regrtest --pgo -j$(( $cpucount * 2 ))" -s
make -j$cpucount -s
RESULT=$?
echo "First make exited with $RESULT"
if [ $RESULT != 0 ]; then
echo "Trying Python compile again without unsafe flags..."
make clean
./configure $safe_flags > /dev/null
make -j$cpucount -s
echo "Sticking with safe Python for now..."
fi
echo "Installing Python..."
make install > /dev/null
cd ~
fi
python=~/python/bin/python3
pip=~/python/bin/pip3
if ([ "${ImageOS}" == "ubuntu20" ]) && [ "${HOSTTYPE}" == "x86_64" ]; then
"${python}" -m pip install --upgrade patchelf-wrapper
"${python}" -m pip install --upgrade staticx
fi
cd $whereibelong
fi

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
oauth2.txt
nobrowser.txt
enabledasa.txt
lastupdatecheck.txt
*.json
*.lck
*.csv

View File

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

View File

@@ -1,31 +0,0 @@
if [[ "$PLATFORM" == "x86_64" ]]; then
export WIX_BITS="x64"
elif [[ "$PLATFORM" == "x86" ]]; then
export WIX_BITS="x86"
fi
echo "compiling GAM with pyinstaller..."
export distpath="dist/"
export gampath="${distpath}gam"
rm -rf $gampath
/c/python/scripts/pyinstaller --clean --noupx --distpath $gampath gam.spec
export gam="${gampath}/gam"
echo "running compiled GAM..."
$gam version
export GAMVERSION=$($gam version simple)
rm $gampath/lastupdatecheck.txt
cp LICENSE $gampath
cp GamCommands.txt $gampath
cp gam-setup.bat $gampath
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM.zip
cwd=$(pwd)
cd "${distpath}"
/c/Program\ Files/7-Zip/7z.exe a -tzip $GAM_ARCHIVE gam -xr!.svn
mv "${GAM_ARCHIVE}" "${cwd}"
cd "${cwd}"
echo "Running WIX candle $WIX_BITS..."
/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.11/bin/candle.exe -arch $WIX_BITS gam.wxs
echo "Done with WIX candle..."
echo "Running WIX light..."
/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.11/bin/light.exe -ext /c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.11/bin/WixUIExtension.dll gam.wixobj -o gam-$GAMVERSION-$GAMOS-$PLATFORM.msi || true;
echo "Done with WIX light..."
rm *.wixpdb

View File

@@ -12,11 +12,11 @@ defaults:
working-directory: src
env:
BUILD_PYTHON_VERSION: "3.10.1"
MIN_PYTHON_VERSION: "3.10.1"
BUILD_OPENSSL_VERSION: "3.0.1"
MIN_OPENSSL_VERSION: "1.1.1l"
PATCHELF_VERSION: "0.13"
OPENSSL_CONFIG_OPTS: no-fips
OPENSSL_INSTALL_PATH: ${{ github.workspace }}/bin/ssl
OPENSSL_SOURCE_PATH: ${{ github.workspace }}/src/openssl
PYTHON_INSTALL_PATH: ${{ github.workspace }}/bin/python
PYTHON_SOURCE_PATH: ${{ github.workspace }}/src/cpython
jobs:
build:
@@ -24,64 +24,53 @@ jobs:
strategy:
matrix:
include:
- os: ubuntu-18.04
- os: ubuntu-22.04
jid: 1
goal: "build"
gamos: "linux"
platform: "x86_64"
- os: ubuntu-20.04
goal: build
arch: x86_64
openssl_archs: linux-x86_64
- os: [self-hosted, linux, arm64]
jid: 2
goal: "build"
gamos: "linux"
platform: "x86_64"
- os: macos-11.0
jid: 3
goal: "build"
gamos: "macos"
platform: "universal2"
- os: windows-2022
goal: build
arch: aarch64
openssl_archs: linux-aarch64
- os: macos-12
jid: 4
goal: "build"
gamos: "windows"
pyarch: "x64"
platform: "x86_64"
goal: build
arch: universal2
openssl_archs: darwin64-x86_64 darwin64-arm64
- os: windows-2022
jid: 5
goal: "build"
gamos: "windows"
platform: "x86"
pyarch: "x86"
- os: ubuntu-20.04
goal: "test"
python: "3.7"
goal: build
arch: Win64
openssl_archs: VC-WIN64A
- os: windows-2022
jid: 6
gamos: "linux"
platform: "x86_64"
- os: ubuntu-20.04
goal: "test"
python: "3.8"
goal: build
arch: Win32
openssl_archs: VC-WIN32
- os: ubuntu-22.04
goal: test
python: "3.7"
jid: 7
gamos: "linux"
platform: "x86_64"
- os: ubuntu-20.04
arch: x86_64
- os: ubuntu-22.04
goal: test
python: "3.8"
jid: 8
arch: x86_64
- os: ubuntu-22.04
goal: test
python: "3.9"
jid: 8
gamos: linux
platform: x86_64
- os: [self-hosted, linux, arm64]
jid: 9
goal: "self-build"
platform: "aarch64"
gamos: linux
- os: [self-hosted, linux, arm]
arch: x86_64
- os: ubuntu-22.04
goal: test
python: "3.11-dev"
jid: 10
goal: "self-build"
platform: "armv7l"
gamos: linux
arch: x86_64
steps:
- uses: actions/checkout@master
with:
persist-credentials: false
@@ -89,190 +78,468 @@ jobs:
- name: Cache multiple paths
uses: actions/cache@v2
if: matrix.goal != 'test'
id: cache-python-ssl
with:
path: |
~/python
~/ssl
key: ${{ matrix.os }}-${{ matrix.jid }}-20220111-03
- name: Set env variables
env:
GAMOS: ${{ matrix.gamos }}
GOAL: ${{ matrix.goal }}
JID: ${{ matrix.jid }}
PLATFORM: ${{ matrix.platform }}
run: |
echo "GAMOS=${GAMOS}" >> $GITHUB_ENV
echo "GOAL=${GOAL}" >> $GITHUB_ENV
echo "JID=${JID}" >> $GITHUB_ENV
echo "PLATFORM=${PLATFORM}" >> $GITHUB_ENV
uname -a
bin
key: gam-${{ matrix.jid }}-20220621
- name: Use pre-compiled Python for testing
if: matrix.python != ''
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
architecture: ${{ matrix.pyarch }}
- name: Install Python on Windows
if: matrix.os == 'windows-2022'
run: |
if ( ${Env:PLATFORM} -eq "x86_64" )
{
Set-Variable -name py_arch -value "-amd64"
}
else
{
Set-Variable -name py_arch -value ""
}
Write-Output "py_arch: $py_arch"
Set-Variable -name python_file -value "python-${Env:BUILD_PYTHON_VERSION}${py_arch}.exe"
Write-Output "python_file: $python_file"
Set-Variable -name python_url -value "https://www.python.org/ftp/python/${Env:BUILD_PYTHON_VERSION}/${python_file}"
Write-Output "python_url: $python_url"
Invoke-WebRequest -Uri $python_url -OutFile $python_file
Start-Process -wait -FilePath $python_file -ArgumentList "/quiet","InstallAllUsers=0","TargetDir=c:\\python","AssociateFiles=1","PrependPath=1"
shell: pwsh
- name: Install packages for test
- name: Set env variables for test
if: matrix.goal == 'test'
env:
JID: ${{ matrix.jid }}
ACTIONS_CACHE: ${{ steps.cache-python-ssl.outputs.cache-hit }}
ACTIONS_GOAL: ${{ matrix.goal }}
run: |
echo "RUNNING: apt update..."
sudo apt-get -qq --yes update > /dev/null
sudo apt-get -qq --yes install swig libpcsclite-dev
- name: Set env variables for pre-compiled Python
if: matrix.goal != 'build'
run: |
export python=$(which python3)
export pip=$(which pip3)
export gam="${python} -m gam"
export PYTHON=$(which python3)
export PIP=$(which pip3)
export gam="${PYTHON} -m gam"
export gampath="$(readlink -e .)"
echo -e "python: $python\npip: $pip\ngam: $gam\ngampath: $gampath"
echo "python=${python}" >> $GITHUB_ENV
echo "pip=${pip}" >> $GITHUB_ENV
echo -e "PYTHON: ${PYTHON}\nPIP: ${PIP}\gam: ${gam}\ngampath: ${gampath}"
echo "PYTHON=${PYTHON}" >> $GITHUB_ENV
echo "PIP=${PIP}" >> $GITHUB_ENV
echo "gam=${gam}" >> $GITHUB_ENV
echo "gampath=${gampath}" >> $GITHUB_ENV
$pip install --upgrade pip
"${python}" -V
"${pip}" -V
echo "JID=${JID}" >> $GITHUB_ENV
echo "ACTIONS_CACHE=${ACTIONS_CACHE}" >> $GITHUB_ENV
echo "ACTIONS_GOAL=${ACTIONS_GOAL}" >> $GITHUB_ENV
- name: Build and install Python and OpenSSL
if: matrix.goal == 'build' && steps.cache-primes.outputs.cache-hit != 'true'
- name: Install necessary Github-hosted Linux packages
if: runner.os == 'Linux' && runner.arch == 'X64'
run: |
set +e
source ../.github/actions/${GAMOS}-before-install.sh
echo "python=$python" >> $GITHUB_ENV
echo "pip=$pip" >> $GITHUB_ENV
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> $GITHUB_ENV
if [ $GAMOS == "macos" ]; then
brew install openssl@1.1 rust
export LDFLAGS="-L$(brew --prefix openssl@1.1)/lib"
export pipoptions='--no-binary :all:'
echo "PATH=$PATH" >> $GITHUB_ENV # keep gnutools for MacOS
export MACOSX_DEPLOYMENT_TARGET="10.9"
export CFLAGS="-arch arm64 -arch x86_64 -I$(brew --prefix openssl@1.1)/include"
echo "pipoptions=${pipoptions}" >> $GITHUB_ENV
echo "MACOSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET}" >> $GITHUB_ENV
echo "CFLAGS=${CFLAGS}" >> $GITHUB_ENV
echo "LDFLAGS=${LDFLAGS}" >> $GITHUB_ENV
fi
echo -e "Python: $python\nPip: $pip\nLD_LIB...: $LD_LIBRARY_PATH"
$pip install --upgrade pip $pipoptions
$pip install --upgrade wheel $pipoptions
echo "RUNNING: apt update..."
sudo apt-get -qq --yes update
sudo apt-get -qq --yes install swig libpcsclite-dev
- name: Set Windows Powershell env variables
if: matrix.goal != 'test' && matrix.os == 'windows-2022' && matrix.platform == 'x86_64'
- 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: runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
with:
arch: ${{ matrix.arch }}
- name: Set Env Variables for build
if: matrix.goal == 'build'
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"
GAM_ARCHIVE_ARCH="x86_64"
WIX_ARCH="x64"
CHOC_OPS=""
elif [[ "${arch}" == "Win32" ]]; then
PYEXTERNALS_PATH="win32"
PYBUILDRELEASE_ARCH="Win32"
GAM_ARCHIVE_ARCH="x86"
WIX_ARCH="x86"
CHOC_OPS="--forcex86"
fi
if [[ "${RUNNER_OS}" == "macOS" ]]; then
#brew install coreutils
#brew install bash
MAKE=make
MAKEOPT="-j$(sysctl -n hw.logicalcpu)"
PERL=perl
echo "MACOSX_DEPLOYMENT_TARGET=10.15" >> $GITHUB_ENV
echo "PYTHON=${PYTHON_INSTALL_PATH}/bin/python3" >> $GITHUB_ENV
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
elif [[ "${RUNNER_OS}" == "Windows" ]]; then
MAKE=nmake
MAKEOPT=""
PERL="c:\strawberry\perl\bin\perl.exe"
echo "PYTHON=${PYTHON_INSTALL_PATH}\python.exe" >> $GITHUB_ENV
echo "GAM_ARCHIVE_ARCH=${GAM_ARCHIVE_ARCH}" >> $GITHUB_ENV
echo "WIX_ARCH=${WIX_ARCH}" >> $GITHUB_ENV
fi
echo "We'll run make with: ${MAKEOPT}"
echo "JID=${jid}" >> $GITHUB_ENV
echo "arch=${arch}" >> $GITHUB_ENV
echo "MAKE=${MAKE}" >> $GITHUB_ENV
echo "MAKEOPT=${MAKEOPT}" >> $GITHUB_ENV
echo "PERL=${PERL}" >> $GITHUB_ENV
echo "PYEXTERNALS_PATH=${PYEXTERNALS_PATH}" >> $GITHUB_ENV
echo "PYBUILDRELEASE_ARCH=${PYBUILDRELEASE_ARCH}" >> $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)
echo "Checking out version ${LATEST_STABLE_TAG}"
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' && 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: |
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' && 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: |
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: |
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)
git checkout "${LATEST_STABLE_TAG}"
export COMPILED_PYTHON_VERSION=${LATEST_STABLE_TAG:1} # Trim the "v" prefix
echo "COMPILED_PYTHON_VERSION=${COMPILED_PYTHON_VERSION}" >> $GITHUB_ENV
- name: Mac/Linux Configure Python
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 \
"${extra_args[@]}"
- name: Windows Get External Python deps
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
choco install nasm --no-progress
$env:PATH="$ENV:PATH;c:\Program Files\NASM\"
cmd /c 'call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && set MAKE=nmake && set > %temp%\vcvars.txt'
Get-Content "$env:temp\vcvars.txt" | Foreach-Object {
if ($_ -match "^(.*?)=(.*)$") {
if ($matches[1] -eq "PATH" -or $matches[1] -eq "PLATFORM") {
continue
}
Set-Content "env:\$($matches[1])" $matches[2]
Add-Content -Path $env:GITHUB_ENV -Value "$($matches[1])=$($matches[2])"
}
}
cd "${env:PYTHON_SOURCE_PATH}"
PCBuild\get_externals.bat
- name: Install PyInstaller
if: matrix.goal != 'test'
- name: Windows overwrite external OpenSSL with local
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
set +e
git clone https://github.com/pyinstaller/pyinstaller.git
cd pyinstaller
# use latest pyinstaller tag version
git fetch --tags
git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
# remove pre-compiled bootloaders so we fail if bootloader compile fails
rm -rf PyInstaller/bootloader/*-*/*
cd bootloader
export DefaultWindowsSDKVersion="10.0.20348.0"
if [ "${PLATFORM}" == "x86" ]; then
TARGETARCH="32bit"
else
TARGETARCH="64bit"
fi
if [ $GAMOS == "macos" ]; then
UNIVERsAL="--universal2"
fi
$python ./waf all --target-arch=$TARGETARCH $UNIVERSAL
cat build/config.log
cd ..
$pip install . $pipoptions
cd "${env:PYTHON_SOURCE_PATH}"
$env:OPENSSL_EXT_PATH = "$(Get-Item externals\openssl-bin-* | Select -exp FullName)\"
echo "External OpenSSL was downloaded to ${env:OPENSSL_EXT_PATH}"
Remove-Item -recurse -force "${env:OPENSSL_EXT_PATH}*"
# Emulate what this script does:
# https://github.com/python/cpython/blob/main/PCbuild/openssl.vcxproj
$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: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' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
pip install --upgrade pip
pip install --upgrade sphinx
sphinx-build --version
- name: Windows Config/Build Python
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
cd "${env:PYTHON_SOURCE_PATH}"
# We need out custom openssl.props which uses OpenSSL 3 DLL names
Copy-Item -Path "${env:GITHUB_WORKSPACE}\src\tools\openssl.props" -Destination PCBuild\
echo "Building for ${env:PYBUILDRELEASE_ARCH}..."
PCBuild\build.bat -m --pgo -c Release -p "${env:PYBUILDRELEASE_ARCH}"
- name: Windows Install Python
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
cd "${env:PYTHON_SOURCE_PATH}"
mkdir "${env:PYTHON_INSTALL_PATH}\lib"
mkdir "${env:PYTHON_INSTALL_PATH}\include"
Copy-Item -Path "PCBuild\${env:PYEXTERNALS_PATH}\*" "${env:PYTHON_INSTALL_PATH}\"
Copy-Item -Path "${env:PYTHON_SOURCE_PATH}\Lib\*" "${env:PYTHON_INSTALL_PATH}\lib\" -recurse
Copy-Item -Path "${env:PYTHON_SOURCE_PATH}\Include\*" "${env:PYTHON_INSTALL_PATH}\include\" -recurse
Copy-Item -Path "${env:PYTHON_SOURCE_PATH}\PC\*.h" "${env:PYTHON_INSTALL_PATH}\include\"
- name: Mac/Linux Build Python
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' && runner.os != 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
cd "${PYTHON_SOURCE_PATH}"
$MAKE altinstall
$MAKE bininstall
- name: Run Python
run: |
"${PYTHON}" -V
- name: Upgrade pip, wheel, etc
run: |
curl -O https://bootstrap.pypa.io/get-pip.py
"${PYTHON}" get-pip.py
"${PYTHON}" -m pip install --upgrade pip
"${PYTHON}" -m pip install --upgrade wheel
"${PYTHON}" -m pip install --upgrade setuptools
- name: Install PyInstaller
if: matrix.goal == 'build'
run: |
git clone https://github.com/pyinstaller/pyinstaller.git
cd pyinstaller
export latest_release=$(git tag --list | grep -v dev | grep -v rc | sort -Vr | head -n1)
git checkout "${latest_release}"
# remove pre-compiled bootloaders so we fail if bootloader compile fails
rm -rvf PyInstaller/bootloader/*-*/*
cd bootloader
if [[ "${arch}" == "Win32" ]]; then
export PYINSTALLER_BUILD_ARGS="--target-arch=32bit"
fi
echo "PyInstaller build arguments: ${PYINSTALLER_BUILD_ARGS}"
"${PYTHON}" ./waf all $PYINSTALLER_BUILD_ARGS
cd ../..
echo "---- Installing PyInstaller ----"
"${PYTHON}" -m pip install pyinstaller
- name: Install pip requirements
run: |
set +e
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U --force-reinstall $pipoptions
$pip install --upgrade -r requirements.txt $pipoptions
# yubikey-manager holds cryptography to old version, force upgrade
$pip install --upgrade --no-deps cryptography $pipoptions
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
if: matrix.goal != 'test'
run: |
set +e
source ../.github/actions/${GAMOS}-install.sh
ls -alRF $gampath
echo "gampath=$gampath" >> $GITHUB_ENV
echo "gam=$gam" >> $GITHUB_ENV
echo -e "GAM: ${gam}\nGAMPATH: ${gampath}\nGAMVERSION: ${GAMVERSION}"
export gampath="./dist/gam"
mkdir -p -v "${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 --distpath="${gampath}" gam.spec
- name: Basic Tests all jobs
run: |
echo -e "python: $python\npip: $pip\ngam: $gam\ngampath: $gampath\n"
$python -m unittest discover --start-directory ./ --pattern "*_test.py" --buffer
touch "${gampath}/nobrowser.txt"
$PYTHON -m unittest discover --start-directory ./ --pattern "*_test.py" --buffer
$gam version extended
export GAMVERSION=$($gam version simple)
echo "GAM Version ${GAMVERSION}"
echo "GAMVERSION=${GAMVERSION}" >> $GITHUB_ENV
- name: Basic Tests build jobs only
if: matrix.goal != 'test'
- name: Linux/MacOS package
if: runner.os != 'Windows' && matrix.goal == 'build'
run: |
$pip install packaging
export vline=$($gam version | grep "Python ")
export python_line=($vline)
export this_python=${python_line[1]}
$python tools/a_atleast_b.py "${this_python}" "${MIN_PYTHON_VERSION}"
export vline=$($gam version extended | grep "OpenSSL ")
export openssl_line=($vline)
export this_openssl="${openssl_line[1]}"
$python tools/a_atleast_b.py "${this_openssl}" "${MIN_OPENSSL_VERSION}"
cp -v LICENSE $gampath
cp -v GamCommands.txt $gampath
if [[ "${RUNNER_OS}" == "macOS" ]]; then
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 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 64-bit Make Static
if: runner.os == 'Linux' && contains(runner.arch, '64') && matrix.goal != 'test'
run: |
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: 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: runner.os == 'Linux' && contains(runner.arch, '64') && matrix.goal != 'test'
run: |
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: runner.os == 'Windows' && matrix.goal != 'test'
run: |
cp -v LICENSE $gampath
cp -v GamCommands.txt $gampath
cp -v gam-setup.bat $gampath
cd dist/
GAM_ARCHIVE="../gam-${GAMVERSION}-windows-${GAM_ARCHIVE_ARCH}.zip"
/c/Program\ Files/7-Zip/7z.exe a -tzip $GAM_ARCHIVE gam "-xr@${GITHUB_WORKSPACE}/.github/actions/package_exclusions.txt" -bb3
cd ..
/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.11/bin/candle.exe -arch "${WIX_ARCH}" gam.wxs
/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.11/bin/light.exe -ext /c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.11/bin/WixUIExtension.dll gam.wixobj -o "gam-${GAMVERSION}-windows-${GAM_ARCHIVE_ARCH}.msi" || true;
rm -v -f *.wixpdb
- name: Basic Tests build jobs only
if: matrix.goal != 'test' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
export voutput=$($gam version extended)
export python_line=$(echo -e "${voutput}" | grep "Python ")
export python_arr=($python_line)
export this_python=${python_arr[1]}
if [[ "${this_python}" != "${COMPILED_PYTHON_VERSION}" ]]; then
echo "ERROR: Tried to compile Python ${COMPILED_PYTHON_VERSION} but ended up with ${this_python}"
exit 1
fi
export openssl_line=$(echo -e "${voutput}" | grep "OpenSSL ")
export openssl_arr=($openssl_line)
export this_openssl="${openssl_arr[1]}"
if [[ "${this_openssl}" != "${COMPILED_OPENSSL_VERSION}" ]]; then
echo "ERROR: Tried to compile OpenSSL ${COMPILED_OPENSSL_VERSION} but ended up with ${this_openssl}"
exit 1
fi
echo "We successfully compiled Python ${this_python} and OpenSSL ${this_openssl}"
- name: Live API tests push only
if: github.event_name == 'push' || github.event_name == 'schedule'
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
@@ -283,8 +550,8 @@ jobs:
$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
@@ -292,18 +559,23 @@ 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-
$gam user $newuser update photo https://dummyimage.com/400x600/000/fff
$gam user $newuser get photo
$gam user $newuser delete photo
$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 create group $newgroup name "GHA $JID group" description "This is a description" isarchived true
$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 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 ""
@@ -318,7 +590,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"
@@ -329,11 +601,12 @@ 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
$gam create feature name VC-$newbase
$gam create building "My Building - $newbase" id $newbuilding floors 1,2,3,4,5,6,7,8,9,10,11,12,14,15 description "No 13th floor here..."
@@ -346,14 +619,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
$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
@@ -369,6 +644,8 @@ jobs:
$gam delete hold "GHA hold $newbase" matter $matterid
$gam update matter $matterid action close
$gam update matter $matterid action delete
#$gam delete user $newuser
#$gam undelete user $newuser
$gam delete user $newuser
$gam print users query "gha.jid=$JID" | $gam csv - gam delete user ~primaryEmail
$gam print mobile
@@ -377,7 +654,7 @@ 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
@@ -386,25 +663,35 @@ jobs:
$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 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 $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

@@ -1,7 +1,4 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
default_language_version:
python: python3.7
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
@@ -27,3 +24,9 @@ repos:
hooks:
- id: pylint
args: [--output-format=colorized]
- repo: https://github.com/asottile/pyupgrade
rev: v2.31.0
hooks:
- id: pyupgrade
args: [--py37-plus]

201
LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,6 +1,6 @@
GAM is a command line tool for Google Workspace admins to manage domain and user settings quickly and easily.
![Build Status](https://github.com/jay0lee/GAM/workflows/Build%20and%20test%20GAM/badge.svg)
![Build Status](https://github.com/GAM-team/GAM/workflows/Build%20and%20test%20GAM/badge.svg)
# Quick Start
@@ -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,14 +28,14 @@ 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
[GitHub Releases]: https://github.com/jay0lee/GAM/releases
[GitHub]: https://github.com/jay0lee/GAM/tree/master
[GitHub Wiki]: https://github.com/jay0lee/GAM/wiki/
[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/
[Google Groups]: http://groups.google.com/group/google-apps-manager

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>)*"
@@ -655,7 +657,7 @@ Specify a collection of ChromeOS devices by directly specifying them
## Collections of Users
Specify a collection of Users by directly specifying them or by specifiying items that will yield a list of users
Specify a collection of Users by directly specifying them or by specifying items that will yield a list of users
<UserTypeEntity> ::=
(all users)|
@@ -680,12 +682,19 @@ Specify a collection of Users by directly specifying them or by specifiying item
## 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>)|
@@ -992,9 +1001,9 @@ gam report <ActivityApplicationName> [todrive]
[filter|filters <String>] [event <String>] [ip <String>]
[groupidfilter <String>]
gam create admin <UserItem> <RoleItem> customer|(org_unit <OrgUnitItem>)
gam create admin <UserItem> <RoleItem> customer|(org_unit <OrgUnitItem>) [condition securitygroup|nonsecuritygroup]
gam delete admin <RoleAssignmentId>
gam print admins [todrive] [user <UserItem>] [role <RoleItem>]
gam print admins [todrive] [user <UserItem>] [role <RoleItem>] [condition]
gam create adminrole <String> privileges all|all_ou|<PrivilegesList> [description <String>]
gam update adminrole <RoleItem> [name <String>] [privileges all|all_ou|<PrivilegesList>] [description <String>]
gam delete adminrole <RoleItem>
@@ -1052,7 +1061,7 @@ gam info org|ou <OrgUnitPath> [nousers|notsuspended|suspended] [children|child]
gam print orgs|ous [todrive] [toplevelonly] [from_parent <OrgUnitPath>] [allfields|(fields <OrgUnitFieldNameList>)]
gam create alias|nickname <EmailAddress> user|group|target <UniqueID>|<EmailAddress> [verifynotinvitable]
gam update alias|nickname <EmailAddress> user|group|target <UniqueID>|<EmailAddress> [verifynotinvitable]
gam update alias|nickname <EmailAddress> user|group|target <UniqueID>|<EmailAddress> [notargetverify]
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)]
@@ -1151,6 +1160,7 @@ gam calendar <CalendarItem> modify <CalendarSettings>+
lastPolicyFetchTime|
lastRegistrationTime|
lastStatusReportTime|
machineextensionpolicies|
machineName|
machinePolicies|
orgUnitPath|
@@ -1209,6 +1219,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>]
@@ -1334,7 +1414,7 @@ gam print chromeversions [todrive]
name|
platform|
version|
<ChromeReleasesOrderByFieldName> ::=
<ChromeReleasesOrderByFieldName> ::=
channel|
endtime|
fraction|
@@ -1349,11 +1429,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>)]
@@ -1363,7 +1443,7 @@ gam show chromeschema [filter <String>]
<DeviceID> ::= devices/<String>
<DeviceType> ::= android|chrome_os|google_sync|ios|linux|mac_os|windows
<DeviceUserID> ::= devices/<String>/deviceUsers/<String>
<DeviceOrderbyFieldName> ::=
<DeviceOrderbyFieldName> ::=
createtime|devicetype|lastsynctime|model|osversion|serialnumber
gam create device serialnumber <String> devicetype <DeviceType> [assetid <String>]
@@ -1422,7 +1502,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>
@@ -1487,6 +1567,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>
@@ -1571,6 +1654,7 @@ gam update vaulthold|hold <HoldItem> matter <MatterItem> [query <QueryVaultCorpu
gam delete vaulthold|hold <HoldItem> matter <MatterItem>
gam info vaulthold|hold <HoldItem> matter <MatterItem>
gam print vaultholds|holds [todrive] [matters <MatterItemList>]
gam <UserTypeEntity> show vaultholds|holds
gam create vaultmatter|matter [name <String>] [description <string>]
[collaborator|collaborators <CollaboratorItemList>]
@@ -1758,11 +1842,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>)*
gam <UserTypeEntity> delete teamdrive <TeamDriveID>
gam <UserTypeEntity> show teamdriveinfo <TeamDriveID> [asadmin]
[hidden <Boolean>]
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

@@ -1,547 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
APACHE HTTP SERVER SUBCOMPONENTS:
The Apache HTTP Server includes a number of subcomponents with
separate copyright notices and license terms. Your use of the source
code for the these subcomponents is subject to the terms and
conditions of the following licenses.
For the mod_mime_magic component:
/*
* mod_mime_magic: MIME type lookup via file magic numbers
* Copyright (c) 1996-1997 Cisco Systems, Inc.
*
* This software was submitted by Cisco Systems to the Apache Group in July
* 1997. Future revisions and derivatives of this source code must
* acknowledge Cisco Systems as the original contributor of this module.
* All other licensing and usage conditions are those of the Apache Group.
*
* Some of this code is derived from the free version of the file command
* originally posted to comp.sources.unix. Copyright info for that program
* is included below as required.
* ---------------------------------------------------------------------------
* - Copyright (c) Ian F. Darwin, 1987. Written by Ian F. Darwin.
*
* This software is not subject to any license of the American Telephone and
* Telegraph Company or of the Regents of the University of California.
*
* Permission is granted to anyone to use this software for any purpose on any
* computer system, and to alter it and redistribute it freely, subject to
* the following restrictions:
*
* 1. The author is not responsible for the consequences of use of this
* software, no matter how awful, even if they arise from flaws in it.
*
* 2. The origin of this software must not be misrepresented, either by
* explicit claim or by omission. Since few users ever read sources, credits
* must appear in the documentation.
*
* 3. Altered versions must be plainly marked as such, and must not be
* misrepresented as being the original software. Since few users ever read
* sources, credits must appear in the documentation.
*
* 4. This notice may not be removed or altered.
* -------------------------------------------------------------------------
*
*/
For the modules\mappers\mod_imagemap.c component:
"macmartinized" polygon code copyright 1992 by Eric Haines, erich@eye.com
For the server\util_md5.c component:
/************************************************************************
* NCSA HTTPd Server
* Software Development Group
* National Center for Supercomputing Applications
* University of Illinois at Urbana-Champaign
* 605 E. Springfield, Champaign, IL 61820
* httpd@ncsa.uiuc.edu
*
* Copyright (C) 1995, Board of Trustees of the University of Illinois
*
************************************************************************
*
* md5.c: NCSA HTTPd code which uses the md5c.c RSA Code
*
* Original Code Copyright (C) 1994, Jeff Hostetler, Spyglass, Inc.
* Portions of Content-MD5 code Copyright (C) 1993, 1994 by Carnegie Mellon
* University (see Copyright below).
* Portions of Content-MD5 code Copyright (C) 1991 Bell Communications
* Research, Inc. (Bellcore) (see Copyright below).
* Portions extracted from mpack, John G. Myers - jgm+@cmu.edu
* Content-MD5 Code contributed by Martin Hamilton (martin@net.lut.ac.uk)
*
*/
/* these portions extracted from mpack, John G. Myers - jgm+@cmu.edu */
/* (C) Copyright 1993,1994 by Carnegie Mellon University
* All Rights Reserved.
*
* Permission to use, copy, modify, distribute, and sell this software
* and its documentation for any purpose is hereby granted without
* fee, provided that the above copyright notice appear in all copies
* and that both that copyright notice and this permission notice
* appear in supporting documentation, and that the name of Carnegie
* Mellon University not be used in advertising or publicity
* pertaining to distribution of the software without specific,
* written prior permission. Carnegie Mellon University makes no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied
* warranty.
*
* CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
* THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
* FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
* SOFTWARE.
*/
/*
* Copyright (c) 1991 Bell Communications Research, Inc. (Bellcore)
*
* Permission to use, copy, modify, and distribute this material
* for any purpose and without fee is hereby granted, provided
* that the above copyright notice and this permission notice
* appear in all copies, and that the name of Bellcore not be
* used in advertising or publicity pertaining to this
* material without the specific, prior written permission
* of an authorized representative of Bellcore. BELLCORE
* MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY
* OF THIS MATERIAL FOR ANY PURPOSE. IT IS PROVIDED "AS IS",
* WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
*/
For the srclib\apr\include\apr_md5.h component:
/*
* This is work is derived from material Copyright RSA Data Security, Inc.
*
* The RSA copyright statement and Licence for that original material is
* included below. This is followed by the Apache copyright statement and
* licence for the modifications made to that material.
*/
/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
rights reserved.
License to copy and use this software is granted provided that it
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
Algorithm" in all material mentioning or referencing this software
or this function.
License is also granted to make and use derivative works provided
that such works are identified as "derived from the RSA Data
Security, Inc. MD5 Message-Digest Algorithm" in all material
mentioning or referencing the derived work.
RSA Data Security, Inc. makes no representations concerning either
the merchantability of this software or the suitability of this
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.
These notices must be retained in any copies of any part of this
documentation and/or software.
*/
For the srclib\apr\passwd\apr_md5.c component:
/*
* This is work is derived from material Copyright RSA Data Security, Inc.
*
* The RSA copyright statement and Licence for that original material is
* included below. This is followed by the Apache copyright statement and
* licence for the modifications made to that material.
*/
/* MD5C.C - RSA Data Security, Inc., MD5 message-digest algorithm
*/
/* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
rights reserved.
License to copy and use this software is granted provided that it
is identified as the "RSA Data Security, Inc. MD5 Message-Digest
Algorithm" in all material mentioning or referencing this software
or this function.
License is also granted to make and use derivative works provided
that such works are identified as "derived from the RSA Data
Security, Inc. MD5 Message-Digest Algorithm" in all material
mentioning or referencing the derived work.
RSA Data Security, Inc. makes no representations concerning either
the merchantability of this software or the suitability of this
software for any particular purpose. It is provided "as is"
without express or implied warranty of any kind.
These notices must be retained in any copies of any part of this
documentation and/or software.
*/
/*
* The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0
* MD5 crypt() function, which is licenced as follows:
* ----------------------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
* ----------------------------------------------------------------------------
*/
For the srclib\apr-util\crypto\apr_md4.c component:
* This is derived from material copyright RSA Data Security, Inc.
* Their notice is reproduced below in its entirety.
*
* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
* rights reserved.
*
* License to copy and use this software is granted provided that it
* is identified as the "RSA Data Security, Inc. MD4 Message-Digest
* Algorithm" in all material mentioning or referencing this software
* or this function.
*
* License is also granted to make and use derivative works provided
* that such works are identified as "derived from the RSA Data
* Security, Inc. MD4 Message-Digest Algorithm" in all material
* mentioning or referencing the derived work.
*
* RSA Data Security, Inc. makes no representations concerning either
* the merchantability of this software or the suitability of this
* software for any particular purpose. It is provided "as is"
* without express or implied warranty of any kind.
*
* These notices must be retained in any copies of any part of this
* documentation and/or software.
*/
For the srclib\apr-util\include\apr_md4.h component:
*
* This is derived from material copyright RSA Data Security, Inc.
* Their notice is reproduced below in its entirety.
*
* Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All
* rights reserved.
*
* License to copy and use this software is granted provided that it
* is identified as the "RSA Data Security, Inc. MD4 Message-Digest
* Algorithm" in all material mentioning or referencing this software
* or this function.
*
* License is also granted to make and use derivative works provided
* that such works are identified as "derived from the RSA Data
* Security, Inc. MD4 Message-Digest Algorithm" in all material
* mentioning or referencing the derived work.
*
* RSA Data Security, Inc. makes no representations concerning either
* the merchantability of this software or the suitability of this
* software for any particular purpose. It is provided "as is"
* without express or implied warranty of any kind.
*
* These notices must be retained in any copies of any part of this
* documentation and/or software.
*/
For the srclib\apr-util\test\testmd4.c component:
*
* This is derived from material copyright RSA Data Security, Inc.
* Their notice is reproduced below in its entirety.
*
* Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All
* rights reserved.
*
* RSA Data Security, Inc. makes no representations concerning either
* the merchantability of this software or the suitability of this
* software for any particular purpose. It is provided "as is"
* without express or implied warranty of any kind.
*
* These notices must be retained in any copies of any part of this
* documentation and/or software.
*/
For the srclib\apr-util\xml\expat\conftools\install-sh component:
#
# install - install a program, script, or datafile
# This comes from X11R5 (mit/util/scripts/install.sh).
#
# Copyright 1991 by the Massachusetts Institute of Technology
#
# Permission to use, copy, modify, distribute, and sell this software and its
# documentation for any purpose is hereby granted without fee, provided that
# the above copyright notice appear in all copies and that both that
# copyright notice and this permission notice appear in supporting
# documentation, and that the name of M.I.T. not be used in advertising or
# publicity pertaining to distribution of the software without specific,
# written prior permission. M.I.T. makes no representations about the
# suitability of this software for any purpose. It is provided "as is"
# without express or implied warranty.
#
For the test\zb.c component:
/* ZeusBench V1.01
===============
This program is Copyright (C) Zeus Technology Limited 1996.
This program may be used and copied freely providing this copyright notice
is not removed.
This software is provided "as is" and any express or implied waranties,
including but not limited to, the implied warranties of merchantability and
fitness for a particular purpose are disclaimed. In no event shall
Zeus Technology Ltd. be liable for any direct, indirect, incidental, special,
exemplary, or consequential damaged (including, but not limited to,
procurement of substitute good or services; loss of use, data, or profits;
or business interruption) however caused and on theory of liability. Whether
in contract, strict liability or tort (including negligence or otherwise)
arising in any way out of the use of this software, even if advised of the
possibility of such damage.
Written by Adam Twiss (adam@zeus.co.uk). March 1996
Thanks to the following people for their input:
Mike Belshe (mbelshe@netscape.com)
Michael Campanella (campanella@stevms.enet.dec.com)
*/
For the expat xml parser component:
Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd
and Clark Cooper
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
====================================================================

1
src/LICENSE Symbolic link
View File

@@ -0,0 +1 @@
../LICENSE

File diff suppressed because it is too large Load Diff

View File

@@ -4,13 +4,13 @@
"scopes": {
"https://www.googleapis.com/auth/admin.contact.delegation": {
"description": "View and manage your Contact Delegation"
},
},
"https://www.googleapis.com/auth/admin.contact.delegation.readonly": {
"description": "View your Contact Delegation"
}
}
}
},
},
"basePath": "",
"baseUrl": "https://admin.googleapis.com/admin/contacts/v1/",
"batchPath": "batch",

View File

@@ -28,8 +28,7 @@ upgrade_only=false
gamversion="latest"
adminuser=""
regularuser=""
gam_glibc_vers="2.31 2.27"
#gam_macos_vers="10.15.6 10.14.6 10.13.6"
gam_glibc_vers="2.31"
while getopts "hd:a:o:b:lp:u:r:v:" OPTION
do
@@ -52,7 +51,7 @@ done
target_dir=${target_dir%/}
update_profile() {
[ $2 -eq 1 ] || [ -f "$1" ] || return 1
[ "$2" -eq 1 ] || [ -f "$1" ] || return 1
grep -F "$alias_line" "$1" > /dev/null 2>&1
if [ $? -ne 0 ]; then
@@ -106,7 +105,7 @@ case $gamos in
echo "This Linux distribution uses glibc $this_glibc_ver"
useglibc="legacy"
for gam_glibc_ver in $gam_glibc_vers; do
if version_gt $this_glibc_ver $gam_glibc_ver; then
if version_gt "$this_glibc_ver" "$gam_glibc_ver"; then
useglibc="glibc$gam_glibc_ver"
echo_green "Using GAM compiled against $useglibc"
break
@@ -114,7 +113,7 @@ case $gamos in
done
case $gamarch in
x86_64) gamfile="linux-x86_64-$useglibc.tar.xz";;
arm64|aarch64) gamfile="linux-arm64-glibc2.28.tar.xz";;
arm64|aarch64) gamfile="linux-aarch64-glibc2.28.tar.xz";;
arm|armv7l) gamfile="linux-armv7l-glibc2.28.tar.xz";;
*)
echo_red "ERROR: this installer currently only supports x86_64, arm and arm64 Linux. Looks like you're running on $gamarch. Exiting."
@@ -137,15 +136,15 @@ case $gamos in
gamfile="-windows-x86_64.zip"
;;
*)
echo_red "Sorry, this installer currently only supports Linux and MacOS. Looks like you're runnning on $gamos. Exiting."
echo_red "Sorry, this installer currently only supports Linux and MacOS. Looks like you're running on $gamos. Exiting."
exit
;;
esac
if [ "$gamversion" == "latest" -o "$gamversion" == "prerelease" -o "$gamversion" == "draft" ]; then
release_url="https://api.github.com/repos/jay0lee/GAM/releases"
release_url="https://api.github.com/repos/GAM-team/GAM/releases"
else
release_url="https://api.github.com/repos/jay0lee/GAM/releases/tags/v$gamversion"
release_url="https://api.github.com/repos/GAM-team/GAM/releases/tags/v$gamversion"
fi
if [ -z ${GHCLIENT+x} ]; then
@@ -155,7 +154,7 @@ else
fi
echo_yellow "Checking GitHub URL $release_url for $gamversion GAM release ($check_type)..."
release_json=$(curl -s $GHCLIENT $release_url 2>&1 /dev/null)
release_json=$(curl -s "$GHCLIENT" "$release_url" 2>&1 /dev/null)
echo_yellow "Getting file and download URL..."
# Python is sadly the nearest to universal way to safely handle JSON with Bash
@@ -205,12 +204,12 @@ if (( $rc != 0 )); then
exit
fi
browser_download_url=$(echo "$release_json" | $pycmd -c "$pycode" browser_download_url $gamversion)
browser_download_url=$(echo "$release_json" | $pycmd -c "$pycode" browser_download_url "$gamversion")
if [[ ${browser_download_url:0:5} = "ERROR" ]]; then
echo_red "${browser_download_url}"
exit
fi
name=$(echo "$release_json" | $pycmd -c "$pycode" name $gamversion)
name=$(echo "$release_json" | $pycmd -c "$pycode" name "$gamversion")
if [[ ${name:0:5} = "ERROR" ]]; then
echo_red "${name}"
exit
@@ -224,13 +223,13 @@ 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"
echo_yellow "Extracting archive to $target_dir"
if [[ "${name}" == *.tar.xz ]]; then
tar xf $temp_archive_dir/$name -C "$target_dir"
tar xf "$temp_archive_dir"/"$name" -C "$target_dir"
else
unzip "${temp_archive_dir}/${name}" -d "${target_dir}"
fi
@@ -294,7 +293,7 @@ while true; do
if [ "$adminuser" == "" ]; then
read -p "Please enter your Google Workspace admin email address: " adminuser
fi
"$target_dir/gam/gam" create project $adminuser
"$target_dir/gam/gam" create project "$adminuser"
rc=$?
if (( $rc == 0 )); then
echo_green "Project creation complete."
@@ -319,7 +318,7 @@ while $project_created; do
read -p "Are you ready to authorize GAM to perform Google Workspace management operations as your admin account? (yes or no) " yn
case $yn in
[Yy]*)
"$target_dir/gam/gam" oauth create $adminuser
"$target_dir/gam/gam" oauth create "$adminuser"
rc=$?
if (( $rc == 0 )); then
echo_green "Admin authorization complete."
@@ -348,7 +347,7 @@ while $project_created; do
read -p "Please enter the email address of a regular Google Workspace user: " regularuser
fi
echo_yellow "Great! Checking service account scopes.This will fail the first time. Follow the steps to authorize and retry. It can take a few minutes for scopes to PASS after they've been authorized in the admin console."
"$target_dir/gam/gam" user $adminuser check serviceaccount
"$target_dir/gam/gam" user "$adminuser" check serviceaccount
rc=$?
if (( $rc == 0 )); then
echo_green "Service account authorization complete."

View File

@@ -12,7 +12,7 @@ extra_files = [(os.path.join(proot, 'cacerts.txt'), 'httplib2')]
extra_files += copy_metadata('google-api-python-client')
extra_files += [('cbcm-v1.1beta1.json', '.')]
extra_files += [('contactdelegation-v1.json', '.')]
extra_files += [('versionhistory-v1.json', '.')]
extra_files += [('admin-directory_v1.1beta1.json', '.')]
hidden_imports = [
'gam.auth.yubikey',
@@ -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
@@ -78,7 +80,9 @@ from gam.gapi.directory import printers as gapi_directory_printers
from gam.gapi.directory import privileges as gapi_directory_privileges
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
@@ -847,7 +851,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()
@@ -1724,134 +1730,6 @@ def doUpdateCourse():
print(f'Updated Course {result["id"]}')
def doDelAdmin():
cd = buildGAPIObject('directory')
roleAssignmentId = sys.argv[3]
print(f'Deleting Admin Role Assignment {roleAssignmentId}')
gapi.call(cd.roleAssignments(),
'delete',
customer=GC_Values[GC_CUSTOMER_ID],
roleAssignmentId=roleAssignmentId)
def doCreateAdmin():
cd = buildGAPIObject('directory')
user = normalizeEmailAddressOrUID(sys.argv[3])
body = {'assignedTo': convertEmailAddressToUID(user, cd)}
role = sys.argv[4]
body['roleId'] = getRoleId(role)
body['scopeType'] = sys.argv[5].upper()
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',
customer=GC_Values[GC_CUSTOMER_ID],
body=body)
def doPrintAdmins():
cd = buildGAPIObject('directory')
roleId = None
todrive = False
kwargs = {}
fields = 'nextPageToken,items(roleAssignmentId,roleId,assignedTo,scopeType,orgUnitId)'
titles = [
'roleAssignmentId', 'roleId', 'role', 'assignedTo', 'assignedToUser',
'scopeType', 'orgUnitId', 'orgUnit'
]
csvRows = []
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'user':
kwargs['userKey'] = normalizeEmailAddressOrUID(sys.argv[i + 1])
i += 2
elif myarg == 'role':
roleId = getRoleId(sys.argv[i + 1])
i += 2
elif myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam print admins')
if roleId and not kwargs:
kwargs['roleId'] = roleId
roleId = None
admins = gapi.get_all_pages(cd.roleAssignments(),
'list',
'items',
customer=GC_Values[GC_CUSTOMER_ID],
fields=fields,
**kwargs)
for admin in admins:
if roleId and roleId != admin['roleId']:
continue
admin_attrib = {}
for key, value in list(admin.items()):
if key == 'assignedTo':
admin_attrib['assignedToUser'] = user_from_userid(value)
elif key == 'roleId':
admin_attrib['role'] = role_from_roleid(value)
elif key == 'orgUnitId':
value = f'id:{value}'
admin_attrib[
'orgUnit'] = gapi_directory_orgunits.orgunit_from_orgunitid(
value, cd)
admin_attrib[key] = value
csvRows.append(admin_attrib)
display.write_csv_file(csvRows, titles, 'Admins', todrive)
def buildRoleIdToNameToIdMap():
cd = buildGAPIObject('directory')
result = gapi.get_all_pages(cd.roles(),
'list',
'items',
customer=GC_Values[GC_CUSTOMER_ID],
fields='nextPageToken,items(roleId,roleName)')
GM_Globals[GM_MAP_ROLE_ID_TO_NAME] = {}
GM_Globals[GM_MAP_ROLE_NAME_TO_ID] = {}
for role in result:
GM_Globals[GM_MAP_ROLE_ID_TO_NAME][role['roleId']] = role['roleName']
GM_Globals[GM_MAP_ROLE_NAME_TO_ID][role['roleName']] = role['roleId']
def role_from_roleid(roleid):
if not GM_Globals[GM_MAP_ROLE_ID_TO_NAME]:
buildRoleIdToNameToIdMap()
return GM_Globals[GM_MAP_ROLE_ID_TO_NAME].get(roleid, roleid)
def roleid_from_role(role):
if not GM_Globals[GM_MAP_ROLE_NAME_TO_ID]:
buildRoleIdToNameToIdMap()
return GM_Globals[GM_MAP_ROLE_NAME_TO_ID].get(role, None)
def getRoleId(role):
cg = UID_PATTERN.match(role)
if cg:
roleId = cg.group(1)
else:
roleId = roleid_from_role(role)
if not roleId:
controlflow.system_error_exit(
4,
f'{role} is not a valid role. Please ensure role name is exactly as shown in admin console.'
)
return roleId
def buildUserIdToNameMap():
cd = buildGAPIObject('directory')
result = gapi.get_all_pages(cd.users(),
@@ -2857,7 +2735,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:
@@ -3734,7 +3612,7 @@ def getDriveFileAttribute(i, body, parameters, myarg, update=False):
i += 2
else:
controlflow.invalid_argument_exit(
myarg, f"gam <users> {operation} drivefile")
myarg, f'gam <users> {operation} drivefile')
return i
@@ -4123,8 +4001,7 @@ def downloadDriveFile(users):
while not done:
status, done = downloader.next_chunk()
if showProgress:
print('Downloaded: {:>7.2%}'.format(
status.progress()))
print(f'Downloaded: {status.progress():>7.2%}')
else:
_, content = drive._http.request(uri=spreadsheetUrl,
method='GET')
@@ -7137,6 +7014,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)
@@ -7246,6 +7125,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
@@ -7256,19 +7136,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.'
)
@@ -8134,10 +8021,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':
@@ -8145,7 +8039,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:
@@ -8153,13 +8047,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):
@@ -8169,7 +8065,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:
@@ -8181,7 +8077,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 = {
@@ -8192,16 +8088,20 @@ TEAMDRIVE_RESTRICTIONS_MAP = {
}
def doUpdateTeamDrive(users):
teamDriveId = sys.argv[5]
def doUpdateSharedDrive(users):
i, driveId = getSharedDriveId(5)
body = {}
useDomainAdminAccess = False
i = 6
change_hide = None
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
@@ -8219,6 +8119,15 @@ 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 ['hidden']:
if getBoolean(sys.argv[i+1], myarg):
change_hide = 'hide'
else:
change_hide = 'unhide'
i += 2
elif myarg in TEAMDRIVE_RESTRICTIONS_MAP:
body.setdefault('restrictions', {})
body['restrictions'][
@@ -8227,30 +8136,40 @@ def doUpdateTeamDrive(users):
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam <users> update teamdrive')
if not body:
'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:
user, drive = buildDrive3GAPIObject(user)
if not drive:
continue
result = gapi.call(drive.drives(),
if body:
result = gapi.call(drive.drives(),
'update',
useDomainAdminAccess=useDomainAdminAccess,
body=body,
driveId=teamDriveId,
driveId=driveId,
fields='id',
soft_errors=True)
if not result:
continue
print(f'Updated Team Drive {teamDriveId}')
if not result:
continue
if change_hide:
ch_result = gapi.call(drive.drives(),
change_hide,
driveId=driveId,
fields='id',
soft_errors=True)
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('_', '')
@@ -8263,13 +8182,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
@@ -8284,12 +8208,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')
@@ -8299,17 +8227,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)
@@ -8439,6 +8388,24 @@ def doRemoveUsersAliases(users):
def doUpdateAlias():
def verify_alias_target_exists():
if target_type != 'group':
try:
gapi.call(cd.users(), 'get',
throw_reasons=[gapi_errors.ErrorReason.USER_NOT_FOUND],
userKey=target_email)
return 'user'
except gapi_errors.GapiUserNotFoundError:
if target_type == 'user':
return None
try:
gapi.call(cd.groups(), 'get',
throw_reasons=[gapi_errors.ErrorReason.GROUP_NOT_FOUND],
groupKey=target_email)
return 'group'
except gapi_errors.GapiGroupNotFoundError:
return None
cd = buildGAPIObject('directory')
alias = normalizeEmailAddressOrUID(sys.argv[3], noUid=True, noLower=True)
target_type = sys.argv[4].lower()
@@ -8446,15 +8413,19 @@ def doUpdateAlias():
controlflow.expected_argument_exit(
'target type', ', '.join(['user', 'group', 'target']), target_type)
target_email = normalizeEmailAddressOrUID(sys.argv[5])
if len(sys.argv) > 6:
myarg = sys.argv[6].lower().replace('_', '')
if myarg != 'verifynotinvitable':
controlflow.system_error_exit(
3,
f'{myarg} is not a valid argument for "gam update alias"'
)
if gapi_cloudidentity_userinvitations.is_invitable_user(alias):
controlflow.system_error_exit(51, f'Alias not updated, {alias} is an unmanaged account')
verifyTarget = True
i = 6
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'noverifytarget':
verifyTarget = False
i += 1
else:
controlflow.system_error_exit(3, f'{myarg} is not a valid argument for "gam update alias"')
if verifyTarget:
target_type = verify_alias_target_exists()
if target_type is None:
controlflow.system_error_exit(51, f'Alias not updated, {target_email} does not exist')
try:
gapi.call(cd.users().aliases(),
'delete',
@@ -9076,8 +9047,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"]}')
@@ -9429,13 +9401,6 @@ def doDeprovUser(users):
print(f'Done deprovisioning {user}')
def doDeleteUser():
cd = buildGAPIObject('directory')
user_email = normalizeEmailAddressOrUID(sys.argv[3])
print(f'Deleting account for {user_email}')
gapi.call(cd.users(), 'delete', userKey=user_email)
def doUndeleteUser():
cd = buildGAPIObject('directory')
user = normalizeEmailAddressOrUID(sys.argv[3])
@@ -10542,6 +10507,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'],
@@ -11226,7 +11196,7 @@ def run_batch(items):
# Otherwise, the argument is preserved as is
#
# SubFields is a dictionary; the key is the argument number, the value is a list of tuples that mark
# the substition (fieldname, start, end).
# the substitution (fieldname, start, end).
# Example: update user '~User' address type work unstructured '~~Street~~, ~~City~~, ~~State~~ ~~ZIP~~' primary
# {2: [('User', 0, 5)], 7: [('Street', 0, 10), ('City', 12, 20), ('State', 22, 31), ('ZIP', 32, 39)]}
#
@@ -11432,7 +11402,7 @@ def ProcessGAMCommand(args):
elif argument in ['domainalias', 'aliasdomain']:
gapi_directory_domainaliases.create()
elif argument == 'admin':
doCreateAdmin()
gapi_directory_roleassignments.create()
elif argument in ['guardianinvite', 'inviteguardian', 'guardian']:
doInviteGuardian()
elif argument in ['project', 'apiproject']:
@@ -11463,6 +11433,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)
@@ -11527,6 +11499,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)
@@ -11610,7 +11584,7 @@ def ProcessGAMCommand(args):
elif command == 'delete':
argument = sys.argv[2].lower()
if argument == 'user':
doDeleteUser()
gapi_directory_users.delete()
elif argument == 'group':
gapi_directory_groups.delete()
elif argument == 'device':
@@ -11636,7 +11610,7 @@ def ProcessGAMCommand(args):
elif argument in ['domainalias', 'aliasdomain']:
gapi_directory_domainaliases.delete()
elif argument == 'admin':
doDelAdmin()
gapi_directory_roleassignments.delete()
elif argument in ['guardian', 'guardians']:
doDeleteGuardian()
elif argument in ['project', 'projects']:
@@ -11667,6 +11641,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)
@@ -11743,7 +11719,7 @@ def ProcessGAMCommand(args):
elif argument in ['domainaliases', 'aliasdomains']:
gapi_directory_domainaliases.print_()
elif argument == 'admins':
doPrintAdmins()
gapi_directory_roleassignments.print_()
elif argument in ['roles', 'adminroles']:
gapi_directory_roles.print_()
elif argument in ['guardian', 'guardians']:
@@ -11788,6 +11764,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)
@@ -11818,6 +11798,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)
@@ -11976,8 +11960,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':
@@ -12020,12 +12004,14 @@ 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']:
gapi_vault.showHoldsForUsers(users)
else:
controlflow.invalid_argument_exit(showWhat, 'gam <users> show')
elif command == 'print':
@@ -12052,8 +12038,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']:
@@ -12136,8 +12122,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:
@@ -12170,8 +12156,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:
@@ -12212,8 +12198,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

@@ -1,26 +1,42 @@
"""OAuth2.0 user credentials."""
import datetime
import ipaddress
import json
import multiprocessing
import os
import re
from socket import gethostbyname
import sys
from time import sleep
import threading
from urllib.parse import urlencode
from urllib.parse import urlencode, urlparse, parse_qs
import wsgiref.simple_server
import wsgiref.util
import webbrowser
from filelock import FileLock
import google_auth_oauthlib.flow
import google.oauth2.credentials
import google.oauth2.id_token
from gam import controlflow
from gam import display
from gam import fileutils
from gam import transport
from gam.var import GM_Globals
from gam.var import GM_WINDOWS
from gam.var import GM_Globals, GM_WINDOWS
from gam import utils
MESSAGE_CONSOLE_AUTHORIZATION_PROMPT = ('\nGo to the following link in your '
'browser:\n\n\t{url}\n')
MESSAGE_CONSOLE_AUTHORIZATION_CODE = 'Enter verification code: '
MESSAGE_CONSOLE_AUTHORIZATION_PROMPT = '''\nGo to the following link in your browser:
\t{url}
IMPORTANT: If you get a browser error that the site can't be reached AFTER you
click the Allow button, copy the URL from the browser where the error occurred
and paste that here instead.
'''
MESSAGE_CONSOLE_AUTHORIZATION_CODE = 'Enter verification code or browser URL: '
MESSAGE_LOCAL_SERVER_AUTHORIZATION_PROMPT = ('\nYour browser has been opened to'
' visit:\n\n\t{url}\n\nIf your '
'browser is on a different machine'
@@ -30,6 +46,8 @@ MESSAGE_LOCAL_SERVER_AUTHORIZATION_PROMPT = ('\nYour browser has been opened to'
MESSAGE_LOCAL_SERVER_SUCCESS = ('The authentication flow has completed. You may'
' close this browser window and return to GAM.')
MESSAGE_AUTHENTICATION_COMPLETE = ('\nThe authentication flow has completed.\n')
class CredentialsError(Exception):
"""Base error class."""
@@ -295,19 +313,8 @@ class Credentials(google.oauth2.credentials.Credentials):
if login_hint:
flow_kwargs['login_hint'] = login_hint
# TODO: Move code for browser detection somewhere in this file so that the
# messaging about `nobrowser.txt` is co-located with the logic that uses it.
if use_console_flow:
flow.run_console(
authorization_prompt_message=
MESSAGE_CONSOLE_AUTHORIZATION_PROMPT,
authorization_code_message=MESSAGE_CONSOLE_AUTHORIZATION_CODE,
flow.run_dual(use_console_flow,
**flow_kwargs)
else:
flow.run_local_server(authorization_prompt_message=
MESSAGE_LOCAL_SERVER_AUTHORIZATION_PROMPT,
success_message=MESSAGE_LOCAL_SERVER_SUCCESS,
**flow_kwargs)
return cls.from_google_oauth2_credentials(flow.credentials,
filename=filename)
@@ -516,10 +523,66 @@ class Credentials(google.oauth2.credentials.Credentials):
http.request(revoke_uri, 'GET')
def _localhost_to_ip():
'''returns IPv4 or IPv6 loopback address which localhost resolves to.
If localhost does not resolve to valid loopback IP address then returns
127.0.0.1'''
# TODO gethostbyname() will only ever return ipv4
# find a way to support IPv6 here and get preferred IP
# note that IPv6 may be broken on some systems also :-(
# for now IPv4 should do.
local_ip = gethostbyname('localhost')
local_ipaddress = ipaddress.ip_address(local_ip)
ip4_local_range = ipaddress.ip_network('127.0.0.0/8')
ip6_local_range = ipaddress.ip_network('::1/128')
if local_ipaddress not in ip4_local_range and \
local_ipaddress not in ip6_local_range:
local_ip = '127.0.0.1'
return local_ip
def _wait_for_http_client(d):
wsgi_app = google_auth_oauthlib.flow._RedirectWSGIApp(MESSAGE_LOCAL_SERVER_SUCCESS)
wsgiref.simple_server.WSGIServer.allow_reuse_address = False
# Convert hostn to IP since apparently binding to the IP
# reduces odds of firewall blocking us
local_ip = _localhost_to_ip()
for port in range(8080, 8099):
try:
local_server = wsgiref.simple_server.make_server(
local_ip,
port,
wsgi_app,
handler_class=wsgiref.simple_server.WSGIRequestHandler
)
break
except OSError:
pass
redirect_uri_format = (
"http://{}:{}/" if d['trailing_slash'] else "http://{}:{}"
)
# provide redirect_uri to main process so it can formulate auth_url
d['redirect_uri'] = redirect_uri_format.format(*local_server.server_address)
# wait until main process provides auth_url
# so we can open it in web browser.
while 'auth_url' not in d:
sleep(0.1)
if d['open_browser']:
webbrowser.open(d['auth_url'], new=1, autoraise=True)
local_server.handle_request()
authorization_response = wsgi_app.last_request_uri.replace("http", "https")
d['code'] = authorization_response
local_server.server_close()
def _wait_for_user_input(d):
sys.stdin = open(0)
code = input(MESSAGE_CONSOLE_AUTHORIZATION_CODE)
d['code'] = code
class _ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
"""InstalledAppFlow which utilizes a URL shortener for authorization URLs."""
URL_SHORTENER_ENDPOINT = 'https://gam-shortn.appspot.com/create'
def authorization_url(self, http=None, **kwargs):
"""Gets a shortened authorization URL."""
@@ -528,6 +591,58 @@ class _ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
return short_url, state
def run_dual(self,
use_console_flow,
authorization_prompt_message='',
console_prompt_message='',
web_success_message='',
open_browser=True,
redirect_uri_trailing_slash=True,
**kwargs):
mgr = multiprocessing.Manager()
d = mgr.dict()
d['trailing_slash'] = redirect_uri_trailing_slash
d['open_browser'] = use_console_flow
http_client = multiprocessing.Process(target=_wait_for_http_client,
args=(d,))
user_input = multiprocessing.Process(target=_wait_for_user_input,
args=(d,))
http_client.start()
# we need to wait until web server starts on avail port
# so we know redirect_uri to use
while 'redirect_uri' not in d:
sleep(0.1)
self.redirect_uri = d['redirect_uri']
d['auth_url'], _ = self.authorization_url(**kwargs)
print(MESSAGE_CONSOLE_AUTHORIZATION_PROMPT.format(url=d['auth_url']))
user_input.start()
userInput = False
while True:
sleep(0.1)
if not http_client.is_alive():
user_input.terminate()
break
elif not user_input.is_alive():
userInput = True
http_client.terminate()
break
while True:
code = d['code']
if code.startswith('http'):
parsed_url = urlparse(code)
parsed_params = parse_qs(parsed_url.query)
code = parsed_params.get('code', [None])[0]
try:
self.fetch_token(code=code)
break
except Exception as e:
if not userInput:
controlflow.system_error_exit(8, str(e))
display.print_error(str(e))
_wait_for_user_input(d)
sys.stdout.write(MESSAGE_AUTHENTICATION_COMPLETE)
return self.credentials
class _FileLikeThreadLock:
"""A threading.lock which has the same interface as filelock.Filelock."""

View File

@@ -44,8 +44,8 @@ class CredentialsTest(unittest.TestCase):
# Remove any credential files that may have been created.
if os.path.exists(self.fake_filename):
os.remove(self.fake_filename)
if os.path.exists('%s.lock' % self.fake_filename):
os.remove('%s.lock' % self.fake_filename)
if os.path.exists(f'{self.fake_filename}.lock'):
os.remove(f'{self.fake_filename}.lock')
super().tearDown()
def test_from_authorized_user_info_only_required_info(self):
@@ -126,7 +126,7 @@ class CredentialsTest(unittest.TestCase):
client_secret=self.fake_client_secret,
filename=self.fake_filename)
self.assertIsInstance(creds._lock, oauth.FileLock)
self.assertEqual(creds._lock.lock_file, '%s.lock' % creds.filename)
self.assertEqual(creds._lock.lock_file, f'{creds.filename}.lock')
def test_credentials_uses_thread_lock_when_filename_not_provided(self):
creds = oauth.Credentials(token=self.fake_token,
@@ -189,6 +189,7 @@ class CredentialsTest(unittest.TestCase):
with self.assertRaises(oauth.InvalidCredentialsFileError):
oauth.Credentials.from_credentials_file(self.fake_filename)
@unittest.skip('disabled for oob fixes')
@patch.object(oauth._ShortURLFlow, 'from_client_config')
def test_from_client_secrets_console_flow(self, mock_flow):
flow_creds = google.oauth2.credentials.Credentials(
@@ -211,6 +212,7 @@ class CredentialsTest(unittest.TestCase):
self.assertEqual(flow_creds.client_secret, creds.client_secret)
self.assertEqual(flow_creds.id_token, creds.id_token)
@unittest.skip('disabled for oob fixes')
@patch.object(oauth._ShortURLFlow, 'from_client_config')
def test_from_client_secrets_local_server_flow(self, mock_flow):
flow_creds = google.oauth2.credentials.Credentials(
@@ -233,6 +235,7 @@ class CredentialsTest(unittest.TestCase):
self.assertEqual(flow_creds.client_secret, creds.client_secret)
self.assertEqual(flow_creds.id_token, creds.id_token)
@unittest.skip('disabled for oob fixes')
@patch.object(oauth._ShortURLFlow, 'from_client_config')
def test_from_client_secrets_uses_login_hint(self, mock_flow):
flow_creds = google.oauth2.credentials.Credentials(
@@ -540,7 +543,7 @@ class CredentialsTest(unittest.TestCase):
client_id=self.fake_client_id,
client_secret=self.fake_client_secret,
filename=self.fake_filename)
lock_file = '%s.lock' % creds.filename
lock_file = f'{creds.filename}.lock'
creds.write()
self.assertTrue(os.path.exists(lock_file))
creds.delete()
@@ -564,9 +567,9 @@ class CredentialsTest(unittest.TestCase):
creds.revoke(http=mock_http)
uri = mock_http.request.call_args[0][0]
self.assertRegex(uri, '^%s' % oauth.Credentials._REVOKE_TOKEN_BASE_URI)
self.assertRegex(uri, f'^{oauth.Credentials._REVOKE_TOKEN_BASE_URI}')
params = uri[uri.index('?'):]
self.assertIn('token=%s' % creds.refresh_token, params)
self.assertIn(f'token={creds.refresh_token}', params)
self.assertEqual('GET', mock_http.request.call_args[0][1])

View File

@@ -79,7 +79,7 @@ class ControlFlowTest(unittest.TestCase):
controlflow.wait_on_failure(
attempt,
total_attempts,
'Attempt #%s' % attempt,
f'Attempt #{attempt}',
# Suppress messages while we make a lot of attempts.
error_print_threshold=total_attempts + 1)
# Wait time may be between 60 and 61 secs, due to rand addition.
@@ -102,7 +102,7 @@ class ControlFlowTest(unittest.TestCase):
for attempt in range(1, total_attempts + 1):
controlflow.wait_on_failure(attempt,
total_attempts,
'Attempt #%s' % attempt,
f'Attempt #{attempt}',
error_print_threshold=threshold)
self.assertEqual(total_attempts - threshold,
mock_stderr_write.call_count)

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

@@ -211,7 +211,7 @@ def update():
browser.update(body)
result = gapi.call(cbcm.chromebrowsers(), 'update', deviceId=device_id,
customer=customer_id, body=browser,
projection='BASIC', fields="deviceId")
projection='BASIC', fields='deviceId')
print(f'Updated browser {result["deviceId"]}')

View File

@@ -76,7 +76,7 @@ def print_members():
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(myarg, "gam print chatmembers")
controlflow.invalid_argument_exit(myarg, 'gam print chatmembers')
if not space:
controlflow.system_error_exit(2,
'space <ChatSpace> is required.')
@@ -117,7 +117,7 @@ def create_message():
body['thread'] = {'name': sys.argv[i+1]}
i += 2
else:
controlflow.invalid_argument_exit(myarg, "gam create chat")
controlflow.invalid_argument_exit(myarg, 'gam create chat')
if not space:
controlflow.system_error_exit(2,
'space <ChatSpace> is required.')
@@ -151,7 +151,7 @@ def delete_message():
name = sys.argv[i+1]
i += 2
else:
controlflow.invalid_argument_exit(myarg, "gam delete chat")
controlflow.invalid_argument_exit(myarg, 'gam delete chat')
if not name:
controlflow.system_error_exit(2,
'name <String> is required.')
@@ -182,7 +182,7 @@ def update_message():
name = sys.argv[i+1]
i += 2
else:
controlflow.invalid_argument_exit(myarg, "gam update chat")
controlflow.invalid_argument_exit(myarg, 'gam update chat')
if not name:
controlflow.system_error_exit(2,
'name <String> is required.')

View File

@@ -203,7 +203,7 @@ def printHistory():
if 'channel' in citem:
citem['channel'] = citem['channel'].lower()
else:
channel_match = re.search(r"\/channels\/([^/]*)", citem['name'])
channel_match = re.search(r'\/channels\/([^/]*)', citem['name'])
if channel_match:
try:
citem['channel'] = channel_match.group(1)
@@ -212,7 +212,7 @@ def printHistory():
if 'platform' in citem:
citem['platform'] = citem['platform'].lower()
else:
platform_match = re.search(r"\/platforms\/([^/]*)", citem['name'])
platform_match = re.search(r'\/platforms\/([^/]*)', citem['name'])
if platform_match:
try:
citem['platform'] = platform_match.group(1)

View File

@@ -179,7 +179,7 @@ def build_schemas(svc=None, sfilter=None):
if fdesc.get('field') == setting_name:
for d in fdesc.get('knownValueDescriptions', []):
if d['value'][prefix_len:] == an:
setting_dict['descriptions'][i] = d['description']
setting_dict['descriptions'][i] = d.get('description', '')
break
break
break

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

@@ -51,13 +51,30 @@ def create():
print(f'Created device {result["response"]["name"]}')
def _get_device_name():
name = sys.argv[3]
def _parse_action(action):
kwargs = {}
i = 3
name = sys.argv[i]
if name == 'id':
name = sys.argv[4]
i += 1
name = sys.argv[i]
i += 1
if not name.startswith('devices/'):
name = f'devices/{name}'
return name
customer = _get_device_customerid()
# bah, inconsistencies in API
if action == 'delete':
kwargs['customer'] = customer
else:
kwargs['body'] = {'customer': customer}
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if action == 'wipe' and myarg == 'removeresetlock':
kwargs['body']['removeResetLock'] = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i], f'gam {action} device')
return name, kwargs
def info():
@@ -80,14 +97,7 @@ def info():
def _generic_action(action, device_user=False):
ci = gapi_cloudidentity.build_dwd()
customer = _get_device_customerid()
name = _get_device_name()
# bah, inconsistencies in API
if action == 'delete':
kwargs = {'customer': customer}
else:
kwargs = {'body': {'customer': customer}}
name, kwargs = _parse_action(action)
if device_user:
endpoint = ci.devices().deviceUsers()
else:
@@ -243,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/-'
@@ -250,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
@@ -309,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

@@ -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

@@ -3,3 +3,6 @@ import gam
def build():
return gam.buildGAPIObject('directory')
def build_beta():
return gam.buildGAPIObject('directory_beta')

View File

@@ -304,6 +304,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:
@@ -739,6 +741,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 +757,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

@@ -9,6 +9,32 @@ from gam.gapi import directory as gapi_directory
from gam.gapi import errors as gapi_errors
def _getAllParentOrgUnitsForUser(user, cd=None):
if not cd:
cd = gapi_directory.build()
parent_path = gapi.call(cd.users(),
'get',
userKey=user,
fields='orgUnitPath',
projection='basic')['orgUnitPath']
if parent_path == '/':
orgUnitPath, orgUnitId = getOrgUnitId('/', cd)
return {orgUnitId: orgUnitPath}
parent_path = encodeOrgUnitPath(makeOrgUnitPathRelative(parent_path))
orgUnits = {}
while True:
result = gapi.call(cd.orgunits(),
'get',
customerId=GC_Values[GC_CUSTOMER_ID],
orgUnitPath=parent_path,
fields='orgUnitId,orgUnitPath,parentOrgUnitId')
orgUnits[result['orgUnitId']] = result['orgUnitPath']
if 'parentOrgUnitId' not in result:
break
parent_path = result['parentOrgUnitId']
return orgUnits
def create():
cd = gapi_directory.build()
name = getOrgUnitItem(sys.argv[3], pathOnly=True, absolutePath=False)
@@ -134,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:
@@ -204,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()):
@@ -222,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])
@@ -331,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

@@ -183,7 +183,7 @@ RESCAL_ARGUMENT_TO_PROPERTY_MAP = {
def printFeatures():
to_drive = False
cd = gapi_directory.build()
titles = []
titles = ['name']
csvRows = []
fieldsList = ['name']
fields = 'nextPageToken,features(%s)'
@@ -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

@@ -0,0 +1,127 @@
import sys
from gam.var import GC_Values, GC_CUSTOMER_ID
import gam
from gam import controlflow
from gam import display
from gam import gapi
from gam.gapi import directory as gapi_directory
from gam.gapi.directory import orgunits as gapi_directory_orgunits
from gam.gapi.directory import roles as gapi_directory_roles
SECURITY_GROUP_CONDITION = "api.getAttribute('cloudidentity.googleapis.com/groups.labels', []).hasAny(['groups.security']) && resource.type == 'cloudidentity.googleapis.com/Group'"
NONSECURITY_GROUP_CONDITION = f'!{SECURITY_GROUP_CONDITION}'
def create():
cd = gapi_directory.build()
user = gam.normalizeEmailAddressOrUID(sys.argv[3])
body = {'assignedTo': gam.convertEmailAddressToUID(user, cd)}
role = sys.argv[4]
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':
cd = gapi_directory.build_beta()
body['condition'] = sys.argv[i+1]
if body['condition'] == 'securitygroup':
body['condition'] = SECURITY_GROUP_CONDITION
elif body['condition'] == 'nonsecuritygroup':
body['condition'] = NONSECURITY_GROUP_CONDITION
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam create admin')
print(f'Giving {user} admin role {role} for {scope}')
gapi.call(cd.roleAssignments(),
'insert',
customer=GC_Values[GC_CUSTOMER_ID],
body=body)
def delete():
cd = gapi_directory.build()
roleAssignmentId = sys.argv[3]
print(f'Deleting Admin Role Assignment {roleAssignmentId}')
gapi.call(cd.roleAssignments(),
'delete',
customer=GC_Values[GC_CUSTOMER_ID],
roleAssignmentId=roleAssignmentId)
def print_():
cd = gapi_directory.build()
roleId = None
todrive = False
kwargs = {}
item_fields = ['roleAssignmentId', 'roleId', 'assignedTo', 'scopeType', 'orgUnitId']
titles = [
'roleAssignmentId', 'roleId', 'role', 'assignedTo', 'assignedToUser',
'scopeType', 'orgUnitId', 'orgUnit'
]
csvRows = []
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower()
if myarg == 'user':
kwargs['userKey'] = gam.normalizeEmailAddressOrUID(sys.argv[i + 1])
i += 2
elif myarg == 'role':
roleId = gapi_directory_roles.getRoleId(sys.argv[i + 1])
i += 2
elif myarg == 'condition':
cd = gapi_directory.build_beta()
item_fields.append('condition')
i += 1
elif myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam print admins')
fields = f'nextPageToken,items({",".join(item_fields)})'
if roleId and not kwargs:
kwargs['roleId'] = roleId
roleId = None
admins = gapi.get_all_pages(cd.roleAssignments(),
'list',
'items',
customer=GC_Values[GC_CUSTOMER_ID],
fields=fields,
**kwargs)
for admin in admins:
if roleId and roleId != admin['roleId']:
continue
admin_attrib = {}
for key, value in list(admin.items()):
if key == 'assignedTo':
admin_attrib['assignedToUser'] = gam.user_from_userid(value)
elif key == 'roleId':
admin_attrib['role'] = gapi_directory_roles.role_from_roleid(value)
elif key == 'orgUnitId':
value = f'id:{value}'
admin_attrib[
'orgUnit'] = gapi_directory_orgunits.orgunit_from_orgunitid(
value, cd)
elif key == 'condition':
if value == SECURITY_GROUP_CONDITION:
value = 'securitygroup'
elif value == NONSECURITY_GROUP_CONDITION:
value = 'nonsecuritygroup'
if key not in titles:
titles.append(key)
admin_attrib[key] = value
csvRows.append(admin_attrib)
display.write_csv_file(csvRows, titles, 'Admins', todrive)

View File

@@ -1,6 +1,13 @@
import sys
from gam.var import GC_Values, GC_CUSTOMER_ID
from gam.var import (
GC_Values,
GC_CUSTOMER_ID,
GM_Globals,
GM_MAP_ROLE_ID_TO_NAME,
GM_MAP_ROLE_NAME_TO_ID,
UID_PATTERN
)
import gam
from gam import controlflow
from gam import display
@@ -9,6 +16,47 @@ from gam.gapi import directory as gapi_directory
from gam.gapi.directory import privileges as gapi_directory_privileges
def buildRoleIdToNameToIdMap(cd=None):
if not cd:
cd = gapi_directory.build()
result = gapi.get_all_pages(cd.roles(),
'list',
'items',
customer=GC_Values[GC_CUSTOMER_ID],
fields='nextPageToken,items(roleId,roleName)')
GM_Globals[GM_MAP_ROLE_ID_TO_NAME] = {}
GM_Globals[GM_MAP_ROLE_NAME_TO_ID] = {}
for role in result:
GM_Globals[GM_MAP_ROLE_ID_TO_NAME][role['roleId']] = role['roleName']
GM_Globals[GM_MAP_ROLE_NAME_TO_ID][role['roleName']] = role['roleId']
def role_from_roleid(roleid):
if not GM_Globals[GM_MAP_ROLE_ID_TO_NAME]:
buildRoleIdToNameToIdMap()
return GM_Globals[GM_MAP_ROLE_ID_TO_NAME].get(roleid, roleid)
def roleid_from_role(role):
if not GM_Globals[GM_MAP_ROLE_NAME_TO_ID]:
buildRoleIdToNameToIdMap()
return GM_Globals[GM_MAP_ROLE_NAME_TO_ID].get(role, None)
def getRoleId(role):
cg = UID_PATTERN.match(role)
if cg:
roleId = cg.group(1)
else:
roleId = roleid_from_role(role)
if not roleId:
controlflow.system_error_exit(
4,
f'{role} is not a valid role. Please ensure role name is exactly as shown in admin console.'
)
return roleId
def getPrivileges(body, privs, action):
all_privileges = gapi_directory_privileges.print_(return_only=True)
if privs == 'ALL':
@@ -30,6 +78,7 @@ def getPrivileges(body, privs, action):
controlflow.invalid_argument_exit(priv,
f'gam {action} adminrole privileges')
def create():
cd = gapi_directory.build()
body = {'roleName': sys.argv[3]}
@@ -55,6 +104,7 @@ def create():
customer=GC_Values[GC_CUSTOMER_ID],
body=body)
def update():
cd = gapi_directory.build()
body = {}
@@ -122,3 +172,4 @@ def print_():
role_attrib[key] = value
csvRows.append(role_attrib)
display.write_csv_file(csvRows, titles, 'Admin Roles', todrive)

View File

@@ -1,11 +1,28 @@
import sys
from time import sleep
import gam
from gam import display
from gam import gapi
from gam.gapi import directory as gapi_directory
from gam.gapi import errors as gapi_errors
def delete():
cd = gapi_directory.build()
user_email = gam.normalizeEmailAddressOrUID(sys.argv[3])
print(f'Deleting account for {user_email}')
try:
gapi.call(cd.users(),
'delete',
userKey=user_email,
throw_reasons=[gapi_errors.ErrorReason.CONDITION_NOT_MET])
except gam.gapi.errors.GapiConditionNotMetError as err:
display.print_error(
f'{err} The user {user_email} may be (or have recently been) on Google Vault Hold and thus not eligible for deletion. You can check holds with "gam user <email> show vaultholds".'
)
def get_primary(email):
'''returns primary email of user or empty if email is not a user primary or
alias address.'''

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

@@ -1,10 +1,9 @@
"""GAPI and OAuth Token related errors methods."""
from enum import Enum
import json
from enum import Enum
from gam import controlflow
from gam import display
from gam import controlflow, display
from gam.var import UTF8
@@ -121,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'
@@ -160,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]
@@ -232,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',
@@ -282,7 +284,7 @@ def get_gapi_error_detail(e,
Args:
e: googleapiclient.HttpError, The HTTP Error received.
soft_errors: Boolean, If true, causes error messages to be surpressed,
soft_errors: Boolean, If true, causes error messages to be suppressed,
rather than sending them to stderr.
silent_errors: Boolean, If true, suppresses and ignores any errors from
being displayed

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

@@ -12,6 +12,7 @@ from gam import display
from gam import fileutils
from gam import gapi
from gam.gapi import storage as gapi_storage
from gam.gapi import directory as gapi_directory
from gam.gapi.directory import orgunits as gapi_directory_orgunits
from gam import utils
@@ -199,12 +200,13 @@ def createExport():
showConfidentialModeContent = None # default to not even set
matterId = None
query = None
useNewExport = None
body = {'exportOptions': {}}
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'matter':
matterId = getMatterItem(v, sys.argv[i + 1])
matterId = getMatterItem(v, sys.argv[i + 1], state='OPEN')
body['matterId'] = matterId
i += 2
elif myarg == 'name':
@@ -212,6 +214,9 @@ def createExport():
i += 2
elif myarg in QUERY_ARGS:
query, i = _build_query(query, myarg, i, query_discovery)
elif myarg == 'usenewexport':
useNewExport = gam.getBoolean(sys.argv[i+1], myarg)
i += 2
elif myarg in ['format']:
export_format = sys.argv[i + 1].upper()
if export_format not in allowed_formats:
@@ -261,6 +266,9 @@ def createExport():
if showConfidentialModeContent is not None:
body['exportOptions'][options_field][
'showConfidentialModeContent'] = showConfidentialModeContent
if useNewExport is not None:
body['exportOptions'][options_field][
'useNewExport'] = useNewExport
results = gapi.call(v.matters().exports(),
'create',
matterId=matterId,
@@ -340,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':
@@ -359,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']
@@ -546,7 +555,7 @@ def convertHoldNameToID(v, nameOrID, matterId):
f'in matter {matterId}')
def convertMatterNameToID(v, nameOrID):
def convertMatterNameToID(v, nameOrID, state=None):
nameOrID = nameOrID.lower()
cg = UID_PATTERN.match(nameOrID)
if cg:
@@ -556,6 +565,7 @@ def convertMatterNameToID(v, nameOrID):
'list',
'matters',
view='BASIC',
state=state,
fields=fields)
for matter in matters:
if matter['name'].lower() == nameOrID:
@@ -563,8 +573,8 @@ def convertMatterNameToID(v, nameOrID):
return None
def getMatterItem(v, nameOrID):
matterId = convertMatterNameToID(v, nameOrID)
def getMatterItem(v, nameOrID, state=None):
matterId = convertMatterNameToID(v, nameOrID, state=state)
if not matterId:
controlflow.system_error_exit(4, f'could not find matter {nameOrID}')
return matterId
@@ -667,6 +677,37 @@ def updateHold():
accountId=accountId)
def showHoldsForUsers(users):
cd = gapi_directory.build()
v = buildGAPIObject()
matterIds = _getAllMatterIds(v)
matterHolds = {}
for matterId in matterIds:
matterHolds[matterId] = gapi.get_all_pages(v.matters().holds(),
'list',
'holds',
fields='holds(holdId,name,accounts(accountId,email),orgUnit),nextPageToken',
matterId=matterId)
totalHolds = 0
for user in users:
user = user.lower()
orgUnits = gapi_directory_orgunits._getAllParentOrgUnitsForUser(user, cd)
for matterId in matterIds:
for hold in matterHolds[matterId]:
if 'orgUnit' in hold:
orgUnitId = hold['orgUnit'].get('orgUnitId')
if orgUnitId in orgUnits:
print(f'FOUND: OrgUnit {orgUnits[orgUnitId]} for user {user} is on hold in matterId {matterId} and holdId {hold["holdId"]} named "{hold["name"]}"')
totalHolds += 1
else:
for account in hold.get('accounts', []):
if (user == account.get('email', '').lower()) or (user == account.get('accountId', '')):
print(f'FOUND: User account {user} is on hold in matterId {matterId} and holdId {hold["holdId"]} named "{hold["name"]}"')
totalHolds += 1
break
sys.stdout.write(f'Total Holds: {totalHolds}\n')
def updateMatter(action=None):
v = buildGAPIObject()
matterId = getMatterItem(v, sys.argv[3])
@@ -790,8 +831,7 @@ def downloadExport():
done = False
while not done:
status, done = downloader.next_chunk()
sys.stdout.write(' Downloaded: {:>7.2%}\r'.format(
status.progress()))
sys.stdout.write(f' Downloaded: {status.progress():>7.2%}\r')
sys.stdout.flush()
sys.stdout.write('\n Download complete. Flushing to disk...\n')
fileutils.close_file(f, True)
@@ -894,6 +934,19 @@ def printExports():
display.write_csv_file(csvRows, titles, 'Vault Exports', todrive)
def _getAllMatterIds(v=None, state='OPEN'):
if not v:
v = buildGAPIObject()
fields = 'matters(matterId),nextPageToken'
results = gapi.get_all_pages(v.matters(),
'list',
'matters',
view='BASIC',
state=state,
fields=fields)
return [matter['matterId'] for matter in results]
def printHolds():
v = buildGAPIObject()
todrive = False
@@ -914,20 +967,15 @@ def printHolds():
else:
controlflow.invalid_argument_exit(myarg, 'gam print holds')
if not matters:
fields = 'matters(matterId),nextPageToken'
matters_results = gapi.get_all_pages(v.matters(),
'list',
'matters',
view='BASIC',
state='OPEN',
fields=fields)
for matter in matters_results:
matterIds.append(matter['matterId'])
matterIds = _getAllMatterIds(v)
else:
for matter in matters:
matterIds.append(getMatterItem(v, matter))
i = 0
matter_count = len(matterIds)
for matterId in matterIds:
sys.stderr.write(f'Retrieving holds for matter {matterId}\n')
i += 1
sys.stderr.write(f'Retrieving holds for matter {matterId} ({i}/{matter_count})\n')
holds = gapi.get_all_pages(v.matters().holds(),
'list',
'holds',

View File

@@ -119,7 +119,7 @@ def dehtml(text):
def indentMultiLineText(message, n=0):
return message.replace('\n', '\n{}'.format(' ' * n)).rstrip()
return message.replace('\n', f"\n{' ' * n}").rstrip()
def flatten_json(structure, key='', path='', flattened=None, listLimit=None):

View File

@@ -8,20 +8,20 @@ import platform
import re
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
GAM_VERSION = '6.13'
GAM_VERSION = '6.22'
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} / '
f'{platform.platform()} {platform.machine()}')
GAM_RELEASES = 'https://github.com/jay0lee/GAM/releases'
GAM_WIKI = 'https://github.com/jay0lee/GAM/wiki'
GAM_ALL_RELEASES = 'https://api.github.com/repos/jay0lee/GAM/releases'
GAM_RELEASES = 'https://github.com/GAM-team/GAM/releases'
GAM_WIKI = 'https://github.com/GAM-team/GAM/wiki'
GAM_ALL_RELEASES = 'https://api.github.com/repos/GAM-team/GAM/releases'
GAM_LATEST_RELEASE = GAM_ALL_RELEASES + '/latest'
GAM_PROJECT_FILEPATH = 'https://raw.githubusercontent.com/jay0lee/GAM/master/src/'
GAM_PROJECT_FILEPATH = 'https://raw.githubusercontent.com/GAM-team/GAM/master/src/'
true_values = ['on', 'yes', 'enabled', 'true', '1']
false_values = ['off', 'no', 'disabled', 'false', '0']
@@ -42,7 +42,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 +116,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 +198,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 +287,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',
@@ -292,6 +303,7 @@ V1_DISCOVERY_APIS = {
API_NAME_MAPPING = {
'directory': 'admin',
'directory_beta': 'admin',
'reports': 'admin',
'datatransfer': 'admin',
'drive3': 'drive',
@@ -300,6 +312,7 @@ API_NAME_MAPPING = {
}
API_VER_MAPPING = {
'accesscontextmanager': 'v1',
'alertcenter': 'v1beta1',
'driveactivity': 'v2',
'calendar': 'v3',
@@ -313,6 +326,7 @@ API_VER_MAPPING = {
'contactdelegation': 'v1',
'datatransfer': 'datatransfer_v1',
'directory': 'directory_v1',
'directory_beta': 'directory_v1.1beta1',
'drive': 'v2',
'drive3': 'v3',
'gmail': 'v1',
@@ -974,6 +988,7 @@ CROS_ARGUMENT_TO_PROPERTY_MAP = {
'notes': ['notes',],
'ordernumber': ['orderNumber',],
'org': ['orgUnitPath',],
'orgunitid': ['orgUnitId',],
'orgunitpath': ['orgUnitPath',],
'osversion': ['osVersion',],
'ou': ['orgUnitPath',],
@@ -999,6 +1014,7 @@ CROS_BASIC_FIELDS_LIST = [
]
CROS_SCALAR_PROPERTY_PRINT_ORDER = [
'orgUnitId',
'orgUnitPath',
'annotatedAssetId',
'annotatedLocation',
@@ -1522,6 +1538,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,4 +1,3 @@
yubikey-manager>=4.0.0
cryptography
distro; sys_platform == 'linux'
filelock
@@ -9,5 +8,6 @@ google-auth>=2.3.2
httplib2>=0.17.0
importlib.metadata; python_version < '3.8'
passlib>=1.7.2
python-dateutil
pathvalidate
python-dateutil
yubikey-manager>=4.0.0

View File

@@ -1,10 +1,10 @@
[metadata]
name = GAM for Google Workspace
version = 6.0.7
version = 6.0.22
description = Command line management for Google Workspaces
long_description = file: readme.md
long_description_content_type = text/markdown
url = https://github.com/jay0lee/GAM
url = https://github.com/GAM-team/GAM
author = Jay Lee
author_email = jay0lee@gmail.com
license = Apache
@@ -13,26 +13,26 @@ keywords = google, oauth2, gsuite, google-apps, google-admin-sdk, google-drive,
classifiers =
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
License :: OSI Approved :: Apache License
[options]
packages = find:
python_requires = >=3.6
python_requires = >= 3.7
install_requires =
cryptography
distro; sys_platform == 'linux'
filelock
google-api-python-client >= 2.1
google-api-python-client >= 2.36
google-auth-httplib2
google-auth-oauthlib >= 0.4.1
google-auth >= 1.11.2
httplib2 >= 0.17.0
google-auth-oauthlib >= 0.4.6
google-auth >= 2.3.3
httplib2 >= 0.20.2
importlib.metadata; python_version < '3.8'
passlib >= 1.7.2
passlib >= 1.7.4
python-dateutil
yubikey-manager >= 4.0.0
pathvalidate

30
src/tools/openssl.props Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>$(opensslIncludeDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<AdditionalLibraryDirectories>$(opensslOutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>ws2_32.lib;libcrypto.lib;libssl.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<PropertyGroup>
<_DLLSuffix>-3</_DLLSuffix>
<_DLLSuffix Condition="$(Platform) == 'ARM'">$(_DLLSuffix)-arm</_DLLSuffix>
<_DLLSuffix Condition="$(Platform) == 'ARM64'">$(_DLLSuffix)-arm64</_DLLSuffix>
<_DLLSuffix Condition="$(Platform) == 'x64'">$(_DLLSuffix)-x64</_DLLSuffix>
</PropertyGroup>
<ItemGroup>
<_SSLDLL Include="$(opensslOutDir)\libcrypto$(_DLLSuffix).dll" />
<_SSLDLL Include="$(opensslOutDir)\libcrypto$(_DLLSuffix).pdb" />
<_SSLDLL Include="$(opensslOutDir)\libssl$(_DLLSuffix).dll" />
<_SSLDLL Include="$(opensslOutDir)\libssl$(_DLLSuffix).pdb" />
</ItemGroup>
<Target Name="_CopySSLDLL" Inputs="@(_SSLDLL)" Outputs="@(_SSLDLL->'$(OutDir)%(Filename)%(Extension)')" AfterTargets="Build">
<Copy SourceFiles="@(_SSLDLL)" DestinationFolder="$(OutDir)" />
</Target>
<Target Name="_CleanSSLDLL" BeforeTargets="Clean">
<Delete Files="@(_SSLDLL->'$(OutDir)%(Filename)%(Extension)')" TreatErrorsAsWarnings="true" />
</Target>
</Project>

View File

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