Compare commits

...

301 Commits
v6.20 ... v6.51

Author SHA1 Message Date
Jay Lee
a6582503f2 Update var.py 2023-03-15 18:31:35 -04:00
Jay Lee
7aecb889d2 Allow setting sakey validity_hours 2023-03-15 20:41:25 +00:00
Jay Lee
c273f87cc7 clear cache to pickup OpenSSL 3.1.0 2023-03-14 10:08:02 -04:00
Jay Lee
76d00c993a Update build.yml 2023-03-06 10:13:17 -05:00
Jay Lee
013b47e6e7 Update build.yml 2023-03-06 09:43:50 -05:00
Jay Lee
9f1e9934ff Update build.yml 2023-03-06 09:29:08 -05:00
Jay Lee
48b218bd9c Update build.yml 2023-03-06 08:40:59 -05:00
Jay Lee
af5baa4f3a Update build.yml 2023-03-06 08:37:37 -05:00
Jay Lee
a2cf38d904 Update build.yml 2023-03-06 07:40:01 -05:00
Jay Lee
185522d943 Update build.yml 2023-03-05 15:29:27 -05:00
Jay Lee
a42e4dd080 openssl security level 2 2023-03-05 15:28:02 -05:00
Ross Scroggs
3a5486889f Update chromepolicy.py (#1610)
split of an empty string returns [''], the API wants []
2023-03-04 11:33:22 -05:00
Jay Lee
1a1f100902 completely disalbe TLS 1.0/1.1 2023-03-03 07:09:46 -05:00
Ross Scroggs
c67b214298 Allow user to optionally specify serial number on resetpiv (#1607) 2023-02-27 11:17:20 -05:00
Ross Scroggs
3ad1d5c661 Give error on invalid subargument; handle no YubiKeys in resetpiv (#1606) 2023-02-27 10:37:22 -05:00
Jay Lee
13400d9bde Update build.yml 2023-02-24 14:20:37 -05:00
Ross Scroggs
048e8dfef5 Add gam version checkrc (#1605)
* Add gam version checkrc

* Fix code

* gam version checkrc cleanup
2023-02-24 10:58:23 -05:00
Ross Scroggs
aaf7a89192 Fix documenataion error (#1604) 2023-02-24 09:45:22 -05:00
Jay Lee
e3ee9135ff Update build.yml 2023-02-23 16:30:23 -05:00
Ross Scroggs
a774fc0beb GCP cleanup (#1602) 2023-02-23 11:44:52 -05:00
Jay Lee
f3429bd537 Update build.yml 2023-02-23 08:50:03 -05:00
Jay Lee
37876acfda Update var.py 2023-02-23 08:17:22 -05:00
Jay Lee
2a6dd0d1a2 fix building iamcredentials 2023-02-22 17:30:10 +00:00
Jay Lee
b0626dd37a improve on gam enable apis 2023-02-17 22:07:36 +00:00
Jay Lee
ed0ed8d7fc fix Id 2023-02-17 20:33:47 +00:00
Jay Lee
d67d999930 enable APIs command for signjwt 2023-02-17 20:32:29 +00:00
Jay Lee
ac79cff6b9 create signjwtserviceaccount 2023-02-17 19:39:02 +00:00
Jay Lee
50aadc6ea7 allow forcing OAuth for service account 2023-02-17 15:40:36 +00:00
Jay Lee
9036d114ed signjwt key_type for key-less service account auth 2023-02-17 15:17:01 +00:00
Jay Lee
75c19104ae fix ipv6 with checkconn 2023-02-15 17:22:34 +00:00
Jay Lee
d9b7f88287 6.42 - build shared drive restrictions dynamically 2023-02-13 21:51:41 +00:00
Jay Lee
ae28c09560 6.41 - fixes #1600 2023-02-11 13:40:59 +00:00
Jay Lee
6ffc738a51 Update gam-install.sh 2023-02-11 08:12:35 -05:00
Jay Lee
82dcc4de6a rebuild to get Python 3.11.2 2023-02-08 10:35:58 -05:00
Jay Lee
f7a426f65a rebuild for OpenSSL 3.0.8 2023-02-07 12:25:31 -05:00
Jay Lee
a94ef78066 fix Vault download filenames 2023-02-06 21:43:33 +00:00
Ross Scroggs
62d738f5c2 copy storagebucket/vault cleanup (#1599) 2023-02-06 15:50:39 -05:00
Jay Lee
1c56a0a608 Update var.py 2023-02-06 09:57:47 -05:00
Jay Lee
dc3976bdda gam copy vaultexport/storagebucket commands 2023-02-06 13:33:26 +00:00
Ross Scroggs
454778b190 print chromeaues/chromeversions cleanup, add print chromeneedsattn (#1598)
* print chromeaues/chromeversions cleanup, add print chromeneedsattn

* Fix typo

* Define new ChromeOS fields
2023-02-03 14:10:35 -05:00
Ross Scroggs
5e78c93b71 Added gam print chromeaues (#1597) 2023-01-30 19:42:28 -05:00
Jay Lee
3aefe21f16 Update build.yml 2023-01-13 11:54:03 -05:00
Jay Lee
0fc7958ccc Update build.yml 2023-01-13 11:14:35 -05:00
Jay Lee
13dc4e74c9 Update build.yml 2023-01-12 11:11:07 -05:00
Jay Lee
a17fa16841 Update var.py 2022-12-28 19:08:01 -05:00
Jay Lee
b13757f5d3 Update var.py 2022-12-28 18:49:37 -05:00
Jay Lee
b9df6f4762 Assured controls SKU 2022-12-28 18:44:20 -05:00
Jay Lee
b7d1c62486 [no ci] let other jobs keep building 2022-12-24 14:29:36 -05:00
Jay Lee
90e5f1b665 use search endpoint when cigroup query is specified 2022-12-18 17:19:39 +00:00
Jay Lee
3132fd7783 another fix for show holds 2022-12-17 23:10:58 +00:00
Jay Lee
87808902e6 cat and allow closed matters on show holds 2022-12-17 22:56:12 +00:00
Jay Lee
fb33d8186e fix Win64 PyInstaller 2022-12-17 18:10:53 +00:00
Jay Lee
8bd2e7f879 Upgrade yubkey for 5.0 release. Fixes #1587 2022-12-17 16:28:41 +00:00
Jay Lee
e66744e3f1 Update build.yml 2022-12-07 10:24:14 -05:00
Ross Scroggs
85f2979313 Fix Chrome schema enum processing (#1582)
Prefix should not include anything after ENUM_
```
        name: RollbackToTargetVersionEnum
          value:
            name: ROLLBACK_TO_TARGET_VERSION_ENUM_ROLLBACK_DISABLED
              number: 1
            name: ROLLBACK_TO_TARGET_VERSION_ENUM_ROLLBACK_AND_RESTORE_IF_POSSIBLE
              number: 3
```
2022-12-02 21:51:39 -05:00
Jay Lee
a85ee9b108 Update build.yml 2022-12-02 12:20:18 -05:00
Jay Lee
9ab2f38436 Update build.yml 2022-12-02 09:57:21 -05:00
Jay Lee
5bcdca4fcc Update build.yml 2022-12-02 09:48:53 -05:00
Jay Lee
729edb65be Ubuntu 20.04 so less users need legacy build 2022-12-01 16:25:38 -05:00
Jay Lee
db8afb769b Update build.yml 2022-12-01 14:09:56 -05:00
Jay Lee
7dfc93892c Update build.yml 2022-12-01 13:47:09 -05:00
Jay Lee
d278cb6939 Update build.yml 2022-12-01 13:14:59 -05:00
Jay Lee
bced5172d2 single spec for one file/folder 2022-12-01 18:05:55 +00:00
Jay Lee
bb5beb66a7 Update build.yml 2022-12-01 12:09:56 -05:00
Jay Lee
f849b6ddb7 --noconfirm to overwrite existing gam folder 2022-11-30 21:12:41 +00:00
Jay Lee
d2733a53a2 fix gampath 2022-11-30 21:04:36 +00:00
Jay Lee
1b1ae44f5d Merge branch 'main' of github.com:GAM-team/GAM 2022-11-30 20:51:53 +00:00
Jay Lee
8515dc2616 Switch to PyInstaller onedir for better performance 2022-11-30 20:51:31 +00:00
Jay Lee
ba7a8d8937 Update build.yml 2022-11-30 09:51:49 -05:00
Jay Lee
d543fb9917 Update build.yml 2022-11-30 09:16:22 -05:00
Ross Scroggs
f4d390b77b Use returnnameonly, it's like returnidonly in create drivefile (#1579)
* Document nameonly in create/update inboundssoprofile

* Use returnnameonly, it's like returnidonly in create drivefile
2022-11-30 09:15:36 -05:00
Jay Lee
ffbce1fd25 no ~~ 2022-11-29 15:01:55 +00:00
Jay Lee
2d78ec6edd merge 2022-11-29 14:48:07 +00:00
Jay Lee
9cacdd166f name_only argument for ssoprofile 2022-11-29 14:43:25 +00:00
Ross Scroggs
9af0a5d843 Code fix, consistency preference (#1578)
* Code fix, consistency preference

* Code cleanup

* Code cleanup for sso assignments

* Fix typo

* Shorten lines
2022-11-22 07:08:15 -05:00
Jay Lee
3313295532 Test with user displayname 2022-11-21 23:50:30 +00:00
Jay Lee
fdf6c147dc fix temperature in chrome telemetry 2022-11-21 23:30:21 +00:00
Jay Lee
323dbd5ca9 Allow setting displayName for users 2022-11-21 23:21:45 +00:00
Jay Lee
d01fd74fa3 merge 2022-11-18 16:08:13 +00:00
Jay Lee
8c33b88e3e Inbound SSO improvements 2022-11-18 16:06:14 +00:00
Jay Lee
5d11397fca Update build.yml 2022-11-16 16:05:11 -05:00
Jay Lee
995321978f Update build.yml 2022-11-16 15:58:56 -05:00
Jay Lee
448789dad0 Update build.yml 2022-11-16 15:39:41 -05:00
Jay Lee
e9ba6819ba Update vault.py 2022-11-16 15:31:07 -05:00
Jay Lee
3056c7b803 Update vault.py 2022-11-16 15:23:53 -05:00
Jay Lee
f2c28fd1f7 Update vault.py 2022-11-16 15:20:49 -05:00
Jay Lee
11e4ff1eb5 Update __init__.py 2022-11-16 15:03:23 -05:00
Jay Lee
81cd74c244 Update build.yml 2022-11-16 13:08:44 -05:00
Jay Lee
faade7c057 Update build.yml 2022-11-16 12:47:49 -05:00
Jay Lee
0032066e1d Update build.yml 2022-11-16 12:33:17 -05:00
Jay Lee
dd938baced Update build.yml 2022-11-16 12:12:01 -05:00
Jay Lee
b835b6ee36 Update build.yml 2022-11-16 11:04:06 -05:00
Jay Lee
3660d65df6 Update build.yml 2022-11-16 10:52:19 -05:00
Ross Scroggs
3e0b4125e0 Code fixup (#1577) 2022-11-16 09:30:17 -05:00
Ross Scroggs
9820a3d81e Inbound SSO documentation; org is synonym for ou and orgunit (#1576)
Are `gam info inboundssoassignment` and `gam delete inboundssoassignment` coming?

Is `gam info inboundssocredentials` coming?
2022-11-15 07:07:04 -05:00
Jay Lee
b670a4cee6 Update build.yml 2022-11-14 21:40:53 -05:00
Jay Lee
a5dd5275c8 Update build.yml 2022-11-14 20:57:35 -05:00
Jay Lee
9b6ad2fa60 prepare 6.31 2022-11-15 01:32:22 +00:00
Jay Lee
1d80028c93 Update build.yml 2022-11-14 20:21:38 -05:00
Jay Lee
a013e95fcf Windows actions doesn\'t like an argument that has / as first char 2022-11-15 00:40:43 +00:00
Jay Lee
eb4d6ece3f Update build.yml 2022-11-14 18:53:37 -05:00
Jay Lee
a50d1ef456 new credentials with inbound sso scope 2022-11-14 17:50:38 -05:00
Jay Lee
c179ed732c login and logout, not signin signout 2022-11-14 21:21:32 +00:00
Jay Lee
a85a313ebb Merge branch 'main' of github.com:GAM-team/GAM 2022-11-14 21:10:11 +00:00
Jay Lee
534ccd275d remove / that seems to break Github Actions 2022-11-14 21:09:59 +00:00
Ross Scroggs
3c3d043276 Sort fields in info group, allow gal as an alias for includeinglobaladdresslist (#1575) 2022-11-14 16:09:28 -05:00
Jay Lee
786adb7c44 remove debug 2022-11-14 21:03:00 +00:00
Jay Lee
bb6c8dc225 more debug for orgunits on windows 2022-11-14 20:49:30 +00:00
Jay Lee
a7cd88b2be Merge branch 'main' of github.com:GAM-team/GAM 2022-11-14 20:32:01 +00:00
Jay Lee
a9fad337e2 debug ou create body 2022-11-14 20:31:48 +00:00
Jay Lee
d4dc1b1589 Update build.yml 2022-11-14 15:19:36 -05:00
Jay Lee
ad94adbb53 more actions 2022-11-14 20:12:21 +00:00
Jay Lee
b692799dcb bash quote fixes 2022-11-14 19:54:31 +00:00
Jay Lee
04dcf47746 rollback shelve (leave lowmem framework) 2022-11-14 19:40:36 +00:00
Jay Lee
aebb3c44fe quick fixes 2022-11-14 19:31:16 +00:00
Jay Lee
8cf345196a Inbound SSO API first take 2022-11-14 19:23:37 +00:00
Jay Lee
173fdb2297 Merge branch 'main' of github.com:GAM-team/GAM 2022-11-02 12:26:57 +00:00
Jay Lee
120db6e7d8 Updated Actions creds 2022-11-02 12:23:01 +00:00
Jay Lee
55555506be Update decrypt.sh 2022-11-01 16:32:15 -04:00
Jay Lee
41965e962d rebuild to pickup OpenSSL 3.0.7 2022-11-01 14:39:36 -04:00
Jay Lee
30fdd00d65 GAM 6.30
To be released soon after OpenSSL 3.0.7...
2022-11-01 07:54:38 -04:00
Ross Scroggs
37e3fd904d Rework getting local-Google time offset (#1572) 2022-10-30 08:22:58 -04:00
Jay Lee
dc22b024b8 Try disabling check hostname on time checks 2022-10-29 11:21:00 -04:00
Jay Lee
f412d5ad4c Update build.yml 2022-10-29 10:12:33 -04:00
Jay Lee
24cfe807e6 Update build.yml 2022-10-28 09:35:42 -04:00
Jay Lee
6a721ac2c1 [no ci] 2022-10-28 09:25:08 -04:00
Jay Lee
4a4b22dfba Update build.yml 2022-10-28 08:30:26 -04:00
Ross Scroggs
6d4524c153 Update var.py (#1571) 2022-10-26 17:36:53 -04:00
Jay Lee
d7b2f82a4a Update build.yml 2022-10-26 11:39:32 -04:00
Jay Lee
844a2fe1e8 Update build.yml 2022-10-26 10:03:44 -04:00
Jay Lee
baf822c685 Update build.yml 2022-10-26 09:59:32 -04:00
Jay Lee
f3169a631c Update build.yml 2022-10-26 08:44:31 -04:00
Jay Lee
d171db36bc Update build.yml 2022-10-26 08:40:01 -04:00
Jay Lee
34c7576cd5 Update build.yml 2022-10-26 08:01:21 -04:00
Jay Lee
f859d0678b Update build.yml 2022-10-25 20:19:09 -04:00
Jay Lee
0986cb3fd9 Update build.yml 2022-10-25 19:17:16 -04:00
Jay Lee
645fd9a135 Update build.yml 2022-10-25 10:15:20 -04:00
Jay Lee
9582e6840a Update build.yml 2022-10-24 18:07:32 -04:00
Jay Lee
a8a9cfb2ab Update build.yml 2022-10-24 18:03:11 -04:00
Jay Lee
5519b33a08 Update build.yml 2022-10-24 17:54:55 -04:00
Jay Lee
976ef0252e Update build.yml 2022-10-24 17:52:13 -04:00
Jay Lee
e6829d0804 Update build.yml 2022-10-24 16:51:58 -04:00
Jay Lee
9f985a7b26 Update build.yml 2022-10-17 15:30:26 -04:00
Jay Lee
a628aeb1a8 Update build.yml 2022-10-17 14:54:35 -04:00
Jay Lee
d81c80b150 Update build.yml 2022-10-17 13:56:42 -04:00
Jay Lee
63ee016691 Update build.yml 2022-10-17 13:51:45 -04:00
Ross Scroggs
4935385572 Pass lock (l) not one (1) to initargs (#1567) 2022-10-17 09:08:42 -04:00
Jay Lee
30069d3039 Update build.yml 2022-10-12 12:00:07 -04:00
Jay Lee
3ef8a5a762 Update var.py 2022-10-12 08:10:38 -04:00
Jay Lee
b12fda5007 Update build.yml 2022-10-11 17:15:03 -04:00
Jay Lee
26925c30c1 Update build.yml 2022-10-07 11:40:59 -04:00
Jay Lee
4085816fa3 Update build.yml 2022-10-07 10:52:12 -04:00
Jay Lee
7e36e5abe6 Update build.yml 2022-10-07 10:23:26 -04:00
Jay Lee
2037189148 Update build.yml 2022-10-07 08:13:43 -04:00
Jay Lee
c7781e66e1 Update build.yml 2022-10-06 22:18:30 -04:00
Jay Lee
8843675ad4 leave Homebrew in place 2022-10-06 22:47:35 +00:00
Jay Lee
c05a1ea6b4 Honor nobrowser.txt on auth 2022-10-06 22:36:25 +00:00
Jay Lee
d9a5ac849b try again with new creds 2022-10-06 17:45:05 +00:00
Jay Lee
51d4c29dd5 update creds 2022-10-06 17:38:01 +00:00
Jay Lee
c2bb9cbdaf Catch revoke and throw nicer error 2022-10-05 17:09:39 +00:00
Lewis Lebentz
d185765831 Add Frontline Worker alias (#1566) 2022-10-05 12:40:53 -04:00
Jay Lee
f57f311f16 Update build.yml 2022-09-27 07:07:21 -04:00
Jay Lee
4c81849c60 Update build.yml 2022-09-26 16:28:42 -04:00
Jay Lee
156c8319d9 Update build.yml 2022-09-26 14:20:54 -04:00
Jay Lee
b8de3310d0 Update build.yml 2022-09-26 13:53:29 -04:00
Jay Lee
f28cf664cb Update build.yml 2022-09-26 13:46:14 -04:00
Jay Lee
02b876155a Allow 'info domain' for delegate admins 2022-09-15 20:03:03 +00:00
Jay Lee
97bd1f71c3 Merge branch 'main' of https://github.com/GAM-team/GAM 2022-09-15 14:46:53 +00:00
Jay Lee
8be4445f0d Fix crm org retrieval 2022-09-15 14:45:02 +00:00
Jay Lee
550cf47db4 Update reports.py 2022-09-15 10:22:29 -04:00
Jay Lee
05d32eec08 Update __init__.py 2022-09-14 19:17:04 -04:00
Jay Lee
59c181eeda Update __init__.py 2022-09-14 19:07:58 -04:00
Jay Lee
dd5fd2a2c3 Update __init__.py 2022-09-14 18:02:59 -04:00
Jay Lee
6ab8fbf538 test with lowmemory.txt 2022-09-14 17:49:40 -04:00
Jay Lee
509919da84 Reduce memory with shelve. Fixes #1560 2022-09-14 18:55:17 +00:00
Jay Lee
04bd5f36a0 Update build.yml 2022-09-07 10:17:17 -04:00
Jay Lee
801f5b7861 Update build.yml 2022-09-06 16:50:18 -04:00
Jay Lee
09d86e1220 Update build.yml 2022-09-02 17:13:40 -04:00
Jay Lee
6110aa1d32 Update build.yml 2022-09-02 16:41:25 -04:00
Jay Lee
11e6c80dbf Update build.yml 2022-09-02 16:31:42 -04:00
Jay Lee
1f32536ff7 Update build.yml 2022-09-02 12:15:07 -04:00
Jay Lee
7979206f21 Update build.yml 2022-09-02 09:45:10 -04:00
Jay Lee
f7901790ad Update build.yml 2022-09-02 09:44:16 -04:00
Jay Lee
7fae16f962 Update build.yml 2022-09-01 14:56:23 -04:00
Jay Lee
1dd76012f8 Update __init__.py 2022-09-01 14:34:55 -04:00
Jay Lee
8fd3f4ee7d Update setup.cfg 2022-08-31 16:27:21 -04:00
Jay Lee
e30b8ed53e Update setup.cfg 2022-08-31 16:26:06 -04:00
Jay Lee
e0960d9113 Update setup.cfg 2022-08-31 16:16:55 -04:00
Jay Lee
35dda1cd34 Update setup.cfg 2022-08-31 15:56:59 -04:00
Jay Lee
ef2253fe58 Update setup.cfg 2022-08-31 15:52:57 -04:00
Jay Lee
ecea3aed7e [no ci] 6.25 2022-08-31 15:52:22 -04:00
Jay Lee
2e81cae271 add roots.pem to MSI 2022-08-31 15:44:06 -04:00
Jay Lee
080eede356 Update devices.py 2022-08-31 14:10:22 -04:00
Jay Lee
fe37c687e4 Update build.yml 2022-08-31 11:59:10 -04:00
Jay Lee
27efef1d9b Update build.yml 2022-08-31 08:41:52 -04:00
Jay Lee
52aa1ac0da Update build.yml 2022-08-31 08:39:18 -04:00
Jay Lee
b5c23fdb83 Update gam.spec 2022-08-31 08:30:53 -04:00
Jay Lee
0b16c9aef4 Default to Google's roots.pem CA file 2022-08-31 08:29:34 -04:00
Jay Lee
3be97acd9c Update build.yml 2022-08-31 08:27:11 -04:00
GitHub Action
8df8e6797f [ci skip] Updated roots.pem 2022-08-31 12:19:58 +00:00
Jay Lee
156ba44656 Update get-roots.yml 2022-08-31 08:19:42 -04:00
Jay Lee
1b3663d60c Update get-roots.yml 2022-08-31 08:15:38 -04:00
Jay Lee
8f0ea2f6a5 Rename get-roots.yaml to get-roots.yml 2022-08-31 08:13:59 -04:00
Jay Lee
5e34b12e5c Update get-roots.yaml 2022-08-31 08:13:11 -04:00
Jay Lee
d124575a91 Update get-roots.yaml 2022-08-31 08:11:35 -04:00
Jay Lee
f5364ab4d0 Download Google roots.pem 2022-08-31 08:10:19 -04:00
Jay Lee
b5580c5649 Update var.py 2022-08-29 17:07:31 -04:00
Jay Lee
e9200ea8fb Update var.py 2022-08-29 17:03:00 -04:00
Jay Lee
2e0c280ea6 Update oauth.py 2022-08-29 17:02:41 -04:00
Ross Scroggs
3948a414b5 Back to client access for user invitations (#1553) 2022-08-24 15:24:38 -04:00
Jay Lee
2c83068605 enable user invite scope by default 2022-08-24 17:53:57 +00:00
Jay Lee
6f6ccad00b further refine gam checkconn 2022-08-22 15:16:40 +00:00
Ross Scroggs
bd18f14137 checkconnection cleanup (#1552) 2022-08-22 09:19:39 -04:00
Jay Lee
d54ca7ee43 Update build.yml 2022-08-20 09:31:46 -04:00
Jay Lee
19452c2461 Fix info customer 2022-08-20 13:10:23 +00:00
Jay Lee
4e2e96a6dd Update build.yml 2022-08-20 08:54:15 -04:00
Jay Lee
7957d131c0 Merge branch 'main' of https://github.com/GAM-team/GAM 2022-08-19 20:37:32 +00:00
Jay Lee
ca9dfaff1d gam checkconn first shot 2022-08-19 20:33:19 +00:00
Ross Scroggs
7e9475791b Handle Reports API bug (#1550)
* Handle Reports API bug

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

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

* Update GamCommands.txt

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

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

* Update URLs

* Add new Product/SKU

* Revert to https://jaylee.us/gam
2022-05-08 19:42:39 -04:00
Ross Scroggs
c0c1355216 Handle missing type field in multivalued schema valus (#1520) 2022-05-04 12:41:35 -04:00
Jay Lee
da3394f3fd Update build.yml 2022-05-03 21:37:27 -04:00
Jay Lee
c856723945 Update var.py 2022-04-29 06:12:36 -04:00
Jay Lee
5e10ccdbdd Update __main__.py 2022-04-29 06:12:00 -04:00
Jay Lee
a49f645647 Update za-bug-report.md 2022-04-29 06:10:51 -04:00
Jay Lee
cf8d714ba7 Update ISSUE_TEMPLATE.txt 2022-04-29 06:10:13 -04:00
Jay Lee
17bb625d0d goodbye git.io :-/ 2022-04-29 06:08:51 -04:00
Ross Scroggs
5e7a858d55 Document delete teamdrive allowitemdeletion (#1517) 2022-04-24 08:00:06 -04:00
Jay Lee
8276474314 GAM 6.21 2022-04-22 18:16:17 -04:00
Jay Lee
d108261654 [test] force shared drive deletion 2022-04-22 19:15:20 +00:00
Jay Lee
aa98be443c create drive file on shared drive 2022-04-22 18:53:02 +00:00
Jay Lee
eecb583f10 Option to force shared drive deletion 2022-04-22 18:48:21 +00:00
Ross Scroggs
809aae27b1 Add option to update cigroup to allow updating a dynamic group to a security group (#1515) 2022-04-17 19:51:15 -04:00
Jay Lee
028ca15498 Create codeql-analysis.yml 2022-04-11 21:45:44 -04:00
47 changed files with 3472 additions and 538 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.

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

Binary file not shown.

View File

@@ -14,4 +14,5 @@ gpg --quiet --batch --yes --decrypt --passphrase="${PASSCODE}" \
--output "${credsfile}" "${gpgfile}"
tar xvvf "${credsfile}" --directory "${gampath}"
ls -l "${gampath}"
rm -rvf "${gpgfile}"
rm -rvf "${credsfile}"

View File

@@ -12,7 +12,7 @@ defaults:
working-directory: src
env:
OPENSSL_CONFIG_OPTS: no-fips
OPENSSL_CONFIG_OPTS: no-fips --api=3.0.0
OPENSSL_INSTALL_PATH: ${{ github.workspace }}/bin/ssl
OPENSSL_SOURCE_PATH: ${{ github.workspace }}/src/openssl
PYTHON_INSTALL_PATH: ${{ github.workspace }}/bin/python
@@ -22,6 +22,7 @@ jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-20.04
@@ -29,65 +30,90 @@ jobs:
goal: build
arch: x86_64
openssl_archs: linux-x86_64
- os: [self-hosted, linux, arm64]
- os: [self-hosted, linux, arm64, gcp]
jid: 2
goal: build
arch: aarch64
openssl_archs: linux-aarch64
- os: [self-hosted, linux, arm]
- os: ubuntu-20.04
jid: 3
goal: build
arch: armv7l
openssl_archs: linux-armv4
- os: macos-11
arch: x86_64
openssl_archs: linux-x86_64
staticx: yes
- os: [self-hosted, linux, arm64, gcp]
jid: 4
goal: build
arch: aarch64
openssl_archs: linux-aarch64
staticx: yes
- os: macos-12
jid: 5
goal: build
arch: universal2
openssl_archs: darwin64-x86_64 darwin64-arm64
- os: windows-2022
jid: 5
jid: 6
goal: build
arch: Win64
openssl_archs: VC-WIN64A
- os: windows-2022
jid: 6
jid: 7
goal: build
arch: Win32
openssl_archs: VC-WIN32
- os: ubuntu-20.04
- os: ubuntu-22.04
goal: test
python: "3.7"
jid: 7
arch: x86_64
- os: ubuntu-20.04
goal: test
python: "3.8"
jid: 8
arch: x86_64
- os: ubuntu-20.04
- os: ubuntu-22.04
goal: test
python: "3.8"
jid: 9
arch: x86_64
- os: ubuntu-22.04
goal: test
python: "3.9"
jid: 9
jid: 10
arch: x86_64
- os: ubuntu-22.04
goal: test
python: "3.10"
jid: 11
arch: x86_64
- os: ubuntu-22.04
goal: test
python: "3.12.0-alpha - 3.12"
jid: 12
arch: x86_64
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v3
with:
persist-credentials: false
fetch-depth: 0
- name: Cache multiple paths
uses: actions/cache@v2
if: matrix.goal == 'build'
uses: actions/cache@v3
id: cache-python-ssl
with:
path: |
bin
key: gam-${{ matrix.jid }}-20220328-01
bin.tar.xz
src/cpython
key: gam-${{ matrix.jid }}-20230314
- name: Untar Cache archive
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
working-directory: ${{ github.workspace }}
run: |
tar xvvf bin.tar.xz
- name: Use pre-compiled Python for testing
if: matrix.python != ''
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python }}
@@ -111,24 +137,18 @@ jobs:
echo "ACTIONS_CACHE=${ACTIONS_CACHE}" >> $GITHUB_ENV
echo "ACTIONS_GOAL=${ACTIONS_GOAL}" >> $GITHUB_ENV
- name: Install necessary hosted Linux packages
if: matrix.os == 'ubuntu-20.04'
- name: Install necessary Github-hosted Linux packages
if: runner.os == 'Linux' && runner.arch == 'X64'
run: |
echo "RUNNING: apt update..."
sudo apt-get -qq --yes update
sudo apt-get -qq --yes install swig libpcsclite-dev
- name: MacOS remove Homebrew
if: matrix.os == 'macos-11'
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: matrix.os == 'macos-11'
if: runner.os == 'macOS'
run: |
# Install latest Rust
curl -fsS -o rust.sh https://sh.rustup.rs
curl --retry 5 --retry-connrefused -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
@@ -136,7 +156,7 @@ jobs:
- name: Windows Configure VCode
uses: ilammy/msvc-dev-cmd@v1
if: matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
with:
arch: ${{ matrix.arch }}
@@ -146,8 +166,10 @@ jobs:
arch: ${{ matrix.arch }}
jid: ${{ matrix.jid }}
openssl_archs: ${{ matrix.openssl_archs }}
staticx: ${{ matrix.staticx }}
run: |
echo "We are running on ${RUNNER_OS}"
LD_LIBRARY_PATH="${OPENSSL_INSTALL_PATH}/lib:${PYTHON_INSTALL_PATH}/lib:/usr/local/lib"
if [[ "${arch}" == "Win64" ]]; then
PYEXTERNALS_PATH="amd64"
PYBUILDRELEASE_ARCH="x64"
@@ -179,21 +201,22 @@ jobs:
MAKE=nmake
MAKEOPT=""
PERL="c:\strawberry\perl\bin\perl.exe"
echo "PYTHON=${PYTHON_INSTALL_PATH}\python.exe" >> $GITHUB_ENV
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${PYTHON_SOURCE_PATH}/PCbuild/${PYEXTERNALS_PATH}"
echo "PYTHON=${PYTHON_SOURCE_PATH}/PCbuild/${PYEXTERNALS_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 "staticx=${staticx}" >> $GITHUB_ENV
echo "arch=${arch}" >> $GITHUB_ENV
echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> $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'
@@ -220,7 +243,7 @@ jobs:
- name: Windows NASM Install
uses: ilammy/setup-nasm@v1
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
- name: Config OpenSSL
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit != 'true'
@@ -232,7 +255,7 @@ jobs:
done
- name: Rename GNU link on Windows
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: bash
run: mv /usr/bin/link /usr/bin/gnulink
@@ -269,7 +292,7 @@ jobs:
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 "CFLAGS=-I${OPENSSL_INSTALL_PATH}/include -arch arm64 -arch x86_64 ${CFLAGS}" >> $GITHUB_ENV
echo "ARCHFLAGS=-arch x86_64 -arch arm64" >> $GITHUB_ENV
else
cd "${GITHUB_WORKSPACE}/src/openssl-${openssl_archs}"
@@ -281,6 +304,7 @@ jobs:
if: matrix.goal == 'build'
run: |
"${OPENSSL_INSTALL_PATH}/bin/openssl" version
"${OPENSSL_INSTALL_PATH}/bin/openssl" version -f
file "${OPENSSL_INSTALL_PATH}/bin/openssl"
- name: Get latest stable Python source
@@ -295,7 +319,7 @@ jobs:
echo "COMPILED_PYTHON_VERSION=${COMPILED_PYTHON_VERSION}" >> $GITHUB_ENV
- name: Mac/Linux Configure Python
if: matrix.goal == 'build' && matrix.os != 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os != 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
cd "${PYTHON_SOURCE_PATH}"
if [[ "${RUNNER_OS}" == "macOS" ]]; then
@@ -312,14 +336,14 @@ jobs:
"${extra_args[@]}"
- name: Windows Get External Python deps
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
cd "${env:PYTHON_SOURCE_PATH}"
PCBuild\get_externals.bat
- name: Windows overwrite external OpenSSL with local
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
cd "${env:PYTHON_SOURCE_PATH}"
@@ -331,14 +355,14 @@ jobs:
$env:OPENSSL_EXT_TARGET_PATH = "${env:OPENSSL_EXT_PATH}${env:PYEXTERNALS_PATH}"
echo "Copying our OpenSSL to ${env:OPENSSL_EXT_TARGET_PATH}"
mkdir "${env:OPENSSL_EXT_TARGET_PATH}\include\openssl\"
Copy-Item -Path "${env:GITHUB_WORKSPACE}/src/openssl-${env:openssl_archs}\LICENSE.txt" -Destination "${env:OPENSSL_EXT_TARGET_PATH}\LICENSE"
Copy-Item -Path "${env:GITHUB_WORKSPACE}/src/openssl-${env:openssl_archs}\LICENSE.txt" -Destination "${env:OPENSSL_EXT_TARGET_PATH}\LICENSE" -Verbose
cp -v "$env:OPENSSL_INSTALL_PATH\lib\*" "${env:OPENSSL_EXT_TARGET_PATH}"
cp -v "$env:OPENSSL_INSTALL_PATH\bin\*" "${env:OPENSSL_EXT_TARGET_PATH}"
cp -v "$env:OPENSSL_INSTALL_PATH\include\openssl\*" "${env:OPENSSL_EXT_TARGET_PATH}\include\openssl\"
cp -v "$env:OPENSSL_INSTALL_PATH\include\openssl\applink.c" "${env:OPENSSL_EXT_TARGET_PATH}\include\"
- name: Windows Install sphinx-build
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
pip install --upgrade pip
@@ -346,40 +370,31 @@ jobs:
sphinx-build --version
- name: Windows Config/Build Python
if: matrix.goal == 'build' && matrix.os == 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
shell: powershell
run: |
cd "${env:PYTHON_SOURCE_PATH}"
# We need out custom openssl.props which uses OpenSSL 3 DLL names
Copy-Item -Path "${env:GITHUB_WORKSPACE}\src\tools\openssl.props" -Destination PCBuild\
Copy-Item -Path "${env:GITHUB_WORKSPACE}\src\tools\openssl.props" -Destination PCBuild\ -Verbose
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' && matrix.os == 'windows-2022' && 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' && matrix.os != 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os != 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
cd "${PYTHON_SOURCE_PATH}"
echo "Running: ${MAKE} ${MAKEOPT}"
$MAKE $MAKEOPT
- name: Mac/Linux Install Python
if: matrix.goal == 'build' && matrix.os != 'windows-2022' && steps.cache-python-ssl.outputs.cache-hit != 'true'
if: matrix.goal == 'build' && runner.os != 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
run: |
cd "${PYTHON_SOURCE_PATH}"
$MAKE altinstall
$MAKE bininstall
export PATH="${PATH}:${PYTHON_INSTALL_PATH}/bin"
echo "PATH=${PATH}" >> $GITHUB_ENV
echo "PATH: ${PATH}"
- name: Run Python
run: |
@@ -387,12 +402,27 @@ jobs:
- name: Upgrade pip, wheel, etc
run: |
curl -O https://bootstrap.pypa.io/get-pip.py
curl --retry 5 --retry-connrefused -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 pip requirements
run: |
if [[ "${RUNNER_OS}" == "macOS" ]]; then
"${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 \
cryptography
"${PYTHON}" -m pip install --force-reinstall --no-deps cryptography*.whl
fi
"${PYTHON}" -m pip install --upgrade -r requirements.txt ${PIP_ARGS}
"${PYTHON}" -m pip list
- name: Install PyInstaller
if: matrix.goal == 'build'
run: |
@@ -403,41 +433,37 @@ jobs:
# 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
case "${arch}" in
"Win32")
export PYINSTALLER_BUILD_ARGS="--target-arch=32bit"
;;
"Win64")
export PYINSTALLER_BUILD_ARGS="--target-arch=64bit"
;;
esac
echo "PyInstaller build arguments: ${PYINSTALLER_BUILD_ARGS}"
"${PYTHON}" ./waf all $PYINSTALLER_BUILD_ARGS
cd ../..
cd ..
echo "---- Installing PyInstaller ----"
"${PYTHON}" -m pip install pyinstaller
- name: Install pip requirements
run: |
set +e
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
"${PYTHON}" -m pip install .
- name: Build GAM with PyInstaller
if: matrix.goal != 'test'
run: |
export gampath="./dist/gam"
if [[ "${staticx}" == "yes" ]]; then
export distpath="./dist/gam"
export gampath="${distpath}"
else
export distpath="./dist"
export gampath="${distpath}/gam"
fi
mkdir -p -v "${gampath}"
if [[ "${RUNNER_OS}" == "macOS" ]]; then
export gampath=$($PYTHON -c "import os; print(os.path.realpath('$gampath'))")
elif [[ "${RUNNER_OS}" == "Windows" ]]; then
# Work around issue where PyInstaller picks up python3.dll from other Python versions
# https://github.com/pyinstaller/pyinstaller/issues/7102
export PATH="/usr/bin"
else
export gampath=$(realpath "${gampath}")
fi
@@ -445,8 +471,22 @@ jobs:
echo "gampath=${gampath}" >> $GITHUB_ENV
echo "gam=${gam}" >> $GITHUB_ENV
echo -e "GAM: ${gam}\nGAMPATH: ${gampath}"
"${PYTHON}" -m PyInstaller --clean --distpath="${gampath}" gam.spec
# TEMP force everything back to one file.
export PYINSTALLER_BUILD_ONEFILE="yes"
export distpath="./dist/gam"
export gampath="${distpath}"
"${PYTHON}" -m PyInstaller --clean --noconfirm --distpath="${distpath}" gam.spec
- name: Copy extra package files
if: matrix.goal == 'build'
run: |
cp -v roots.pem $gampath
cp -v LICENSE $gampath
cp -v GamCommands.txt $gampath
if [[ "${RUNNER_OS}" == "Windows" ]]; then
cp -v gam-setup.bat $gampath
fi
- name: Basic Tests all jobs
run: |
$PYTHON -m unittest discover --start-directory ./ --pattern "*_test.py" --buffer
@@ -456,10 +496,8 @@ jobs:
echo "GAMVERSION=${GAMVERSION}" >> $GITHUB_ENV
- name: Linux/MacOS package
if: matrix.os != 'windows-2022' && matrix.goal == 'build'
if: runner.os != 'Windows' && matrix.goal == 'build' && matrix.staticx != 'yes'
run: |
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
@@ -468,35 +506,41 @@ jobs:
fi
tar -C dist/ --create --verbose --exclude-from "${GITHUB_WORKSPACE}/.github/actions/package_exclusions.txt" --file $GAM_ARCHIVE --xz gam
- name: Linux install patchelf/staticx
if: matrix.os == 'ubuntu-20.04' && matrix.goal != 'test'
- name: Install StaticX
if: matrix.staticx == 'yes'
run: |
"${PYTHON}" -m pip install --upgrade patchelf-wrapper
"${PYTHON}" -m pip install --upgrade staticx
- name: Linux Make Static
if: matrix.os == 'ubuntu-20.04' && matrix.goal != 'test'
- name: Make StaticX
if: matrix.staticx == 'yes'
run: |
$PYTHON -m staticx "${gam}" "${gam}-staticx"
case $RUNNER_ARCH in
X64)
ldlib=/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
;;
ARM64)
ldlib=/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1
;;
esac
echo "ldlib=${ldlib}"
$PYTHON -m staticx -l "${ldlib}" "${gam}" "${gam}-staticx"
- name: Linux Run StaticX-ed
if: matrix.os == 'ubuntu-20.04' && matrix.goal != 'test'
- name: Run StaticX
if: matrix.staticx == 'yes'
run: |
"${gam}-staticx" version extended
mv -v "${gam}-staticx" "${gam}"
- name: Linux package staticx
if: matrix.os == 'ubuntu-20.04' && matrix.goal != 'test'
if: matrix.staticx == 'yes'
run: |
GAM_ARCHIVE="gam-${GAMVERSION}-linux-x86_64-legacy.tar.xz"
GAM_ARCHIVE="gam-${GAMVERSION}-linux-$(uname -m)-legacy.tar.xz"
tar -C dist/ --create --verbose --exclude-from "${GITHUB_WORKSPACE}/.github/actions/package_exclusions.txt" --file $GAM_ARCHIVE --xz gam
- name: Windows package
if: matrix.os == 'windows-2022' && matrix.goal != 'test'
if: runner.os == 'Windows' && matrix.goal != 'test'
run: |
cp -v LICENSE $gampath
cp -v GamCommands.txt $gampath
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
@@ -533,29 +577,43 @@ jobs:
if [[ "${RUNNER_OS}" == "macOS" ]]; then
brew install gnupg
fi
source ../.github/actions/decrypt.sh ../.github/actions/creds.tar.gpg creds.tar
source ../.github/actions/decrypt.sh ../.github/actions/creds.tar.xz.gpg creds.tar.xz
export OAUTHFILE="oauth2.txt-gam-gha-${JID}"
echo "OAUTHFILE=${OAUTHFILE}" >> $GITHUB_ENV
export gam_user="gam-gha-${JID}@pdl.jaylee.us"
echo "gam_user=${gam_user}" >> $GITHUB_ENV
$gam checkconn
$gam oauth info
$gam info domain
$gam oauth refresh
$gam info user
#$gam info user $gam_user grouptree
export tstamp=$($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
export newbuilding=$newbase-building
export newresource=$newbase-resource
export newbase="gha_test_${JID}_${tstamp}"
export newuser="${newbase}@pdl.jaylee.us"
export newgroup="${newbase}-group@pdl.jaylee.us"
export newalias="${newbase}-alias@pdl.jaylee.us"
export newbuilding="${newbase}-building"
export newresource="${newbase}-resource"
export newou="aaaGithub Actions/${newbase}"
# cleanup old runs
GAM_CSV_ROW_FILTER="name:regex:gha_test_${JID}_" $gam print vaultholds | $gam csv - gam delete vaulthold "id:~~holdId~~" matter "id:~~matterId~~"
GAM_CSV_ROW_FILTER="name:regex:gha_test_${JID}_" $gam print features | $gam csv - gam delete feature ~name
GAM_CSV_ROW_FILTER="name:regex:^gha_test_${JID}_" $gam user $gam_user print shareddrives asadmin | $gam csv - gam user $gam_user delete shareddrive ~id nukefromorbit
$gam print users query "gha.jid=$JID" | $gam csv - gam delete user ~primaryEmail
GAM_CSV_ROW_FILTER="name:regex:^gha_test_${JID}_" $gam print ous fromparent "aaaGithub Actions" | $gam csv - gam delete ou ~orgUnitId
GAM_CSV_ROW_FILTER="groupKey.id:regex:^gha_test_${JID}_" $gam print cigroups | $gam csv - gam delete cigroup ~groupKey.id
GAM_CSV_ROW_FILTER="resourceId:regex:^gha_test_${JID}_" $gam print resources | $gam csv - gam delete resource ~resourceId
GAM_CSV_ROW_FILTER="buildingId:regex:^gha_test_${JID}_" $gam print buildings | $gam csv - gam delete building ~buildingId
echo "Creating OrgUnit ${newou}"
$gam create ou "${newou}"
export GAM_THREADS=5
echo email > sample.csv;
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 create user $newuser firstname GHA lastname $JID displayname "Github Actions ${JID}" password random ou "${newou}" 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
@@ -568,9 +626,9 @@ jobs:
$gam info cigroup $newgroup
$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 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 ou "${newou}"
$gam csv sample.csv gam update user ~~email~~ recoveryphone 12125121110 recoveryemail jay0lee@gmail.com password random displayname "GitHub Actions Bulk ${JID}"
$gam csv sample.csv gam update user ~~email~~ recoveryphone "" recoveryemail ""
$gam csv sample.csv gam user ~email add license workspaceenterpriseplus
$gam csv sample.csv gam user $gam_user sendemail recipient ~~email~~@pdl.jaylee.us subject "test message $newbase" message "GHA test message"
@@ -583,8 +641,8 @@ jobs:
$gam user $newuser imap on
$gam user $newuser show imap
$gam user $newuser show delegates
$gam user $newuser add contactdelegate "${newbase}-bulkuser-1"
$gam user $newuser print contactdelegates
#$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"
$gam user $newuser show labels
@@ -600,8 +658,8 @@ jobs:
$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 feature name Whiteboard-$newbase
$gam create building "My Building - $newbase" id $newbuilding floors 1,2,3,4,5,6,7,8,9,10,11,12,14,15 description "No 13th floor here..."
$gam create resource $newresource "Resource Calendar $tstamp" capacity 25 features Whiteboard-$newbase,VC-$newbase building $newbuilding floor 15 type Room
$gam info resource $newresource
@@ -621,7 +679,7 @@ jobs:
$gam print vaultmatters matterstate open
$gam print vaultholds matter $matterid
$gam print vaultcount matter $matterid corpus mail everyone todrive
$gam create vaultexport matter $matterid name "GHA export $newbase" corpus mail accounts $newuser use_new_export true
$gam create vaultexport matter $matterid name "GHA export $newbase" corpus mail accounts $newuser use_new_export false
$gam print exports matter $matterid | $gam csv - gam info export $matterid id:~~id~~
$gam csv sample.csv gam user ~email add calendar id:$newresource
$gam delete resource $newresource
@@ -637,8 +695,9 @@ 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
# shakes off vault hold on user so we can delete
$gam print users query "email:${newuser}" orgunitpath | $gam csv - gam update user ~primaryEmail ou ~orgUnitPath
$gam user $newuser show holds
$gam delete user $newuser
$gam print users query "gha.jid=$JID" | $gam csv - gam delete user ~primaryEmail
$gam print mobile
@@ -661,36 +720,69 @@ jobs:
$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 update shareddrive "${driveid}" ou "id:03ph8a2z1t2ph5z"
$gam user $gam_user add drivefile localfile gam.py parentid "${driveid}"
$gam user $gam_user update shareddrive "${driveid}" ou "${newou}"
$gam user $gam_user show shareddrives asadmin
$gam user $gam_user delete shareddrive "${driveid}"
$gam user $gam_user update shareddrive "${driveid}" ou "aaaGithub Actions" # so we can delete our OU...
$gam user $gam_user delete shareddrive "${driveid}" nukefromorbit
echo "printer model count:"
$gam print printermodels | wc -l
#ssoprofile=$($gam create inboundssoprofile name "El Goog ${newbase}" loginurl https://www.google.com logouturl https://www.google.com changepasswordurl https://www.google.com entityid ElGoog return_name_only)
#$gam create inboundssocredential profile "id:${ssoprofile}" generate_key
#$gam create inboundssoassignment profile "id:${ssoprofile}" orgunit "${newou}" mode SAML_SSO
$gam delete ou "${newou}"
#$gam delete inboundssoprofile "id:${ssoprofile}"
#$gam print printers
#$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
export CUSTOMER_ID="C01wfv983"
export GA_DOMAIN="pdl.jaylee.us"
touch $gampath/enabledasa.txt
echo "using delegated admin service account"
$gam print users
- name: Archive production artifacts
uses: actions/upload-artifact@v2
if: github.event_name == 'push' && matrix.goal != 'test'
uses: actions/upload-artifact@v3
if: (github.event_name == 'push' || github.event_name == 'schedule') && matrix.goal != 'test'
with:
name: gam-binaries
path: |
src/*.tar.xz
src/*.zip
src/*.msi
- name: Tar Cache archive
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}
run: |
tar cJvvf bin.tar.xz bin/
publish:
if: github.event_name == 'push'
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v3
with:
persist-credentials: false
fetch-depth: 0
- name: Download artifacts
uses: actions/download-artifact@v3
- name: VirusTotal Scan
uses: crazy-max/ghaction-virustotal@v3
with:
vt_api_key: ${{ secrets.VT_API_KEY }}
files: |
gam-binaries/*
- uses: "marvinpinto/action-automatic-releases@latest"
name: Publish draft release
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
automatic_release_tag: latest
prerelease: false
draft: true
files: |
gam-binaries/*

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

36
.github/workflows/get-roots.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Check for Google Root CA Updates
on:
push:
pull_request:
schedule:
- cron: '23 23 * * *'
defaults:
run:
shell: bash
working-directory: src
jobs:
check-apis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
with:
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
- name: Check for updates
run: curl -o ./roots.pem -vvvv https://pki.goog/roots.pem
- name: Commit file
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add roots.pem
git diff --quiet && git diff --staged --quiet || git commit -am '[ci skip] Updated roots.pem'
- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

View File

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

View File

@@ -653,7 +653,8 @@ Specify a collection of ChromeOS devices by directly specifying them
(crosfile <FileName>)|
(croscsvfile <FileName>:<FieldName>)|
(crosquery <QueryCrOS>)|
(crosqueries <QueryCrOSList>)
(crosqueries <QueryCrOSList>)|
(cros_ou|cros_ou_and_children <OrgUnitPath>)
## Collections of Users
@@ -682,12 +683,19 @@ Specify a collection of Users by directly specifying them or by specifying items
## Item attributes
<BuildingAttribute> ::=
(address|addresslines <String>)|
(city|locality <String>)|
(country|regioncode <String>)|
(description <String>)|
(floors <FloorNameList>)|
(id <String>)|
(language|languageCode <Language>)|
(latitude <Float>)|
(longitude <Float>)|
(name <String>)
(state|administrativearea <String>)|
(sublocality <String>)|
(zipcode|postalcode <String>)
<CalendarAttribute> ::=
(selected <Boolean>)|(hidden <Boolean>)|(summary <String>)|(colorindex|colorid <CalendarColorIndex>)|(backgroundcolor <ColorValue>)|(foregroundcolor <ColorValue>)|
@@ -864,6 +872,7 @@ Specify a collection of Users by directly specifying them or by specifying items
<UserBasicAttribute>|
<UserMultiAttribute>
gam checkconnection
gam version [check|checkrc|simple|extended] [timeoffset] [location <HostName>]
gam help
@@ -902,6 +911,12 @@ gam oauth|oauth2 refresh
gam <UserTypeEntity> check serviceaccount [scope|scopes <APIScopeURLList>]
gam yubikey resetpiv [yubikeyserialnumber <Number>]
gam rotate sakey yubikey yubikey_pin yubikey_slot AUTHENTICATION yubikeyserialnumber <Number>
gam create [gcpserviceaccount|signjwtserviceaccount]
gam enable apis [auto|manual]
gam whatis <EmailItem>
<ResoldCustomerAttribute> ::=
@@ -1059,9 +1074,9 @@ gam delete alias|nickname [user|group|target] <UniqueID>|<EmailAddress>
gam info alias|nickname <EmailAddress>
gam print aliases|nicknames [todrive] [shownoneditable] [nogroups] [nousers] [(query <QueryUser>)|(queries <QueryUserList)]
gam calendar <CalendarItem> add <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>) [sendnotifications <Boolean>]
gam calendar <CalendarItem> add <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domain|default [sendnotifications <Boolean>]
gam calendar <CalendarItem> update <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domain|default [sendnotifications <Boolean>]
gam calendar <CalendarItem> del|delete ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domainx|default
gam calendar <CalendarItem> del|delete ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domain|default
gam calendar <CalendarItem> del|delete id <CalendarACLRuleID>
gam calendar <CalendarItem> showacl
gam calendar <CalendarItem> printacl [todrive]
@@ -1296,7 +1311,9 @@ gam update chatmessage name <String>
deprovision_retiring_device|
deprovision_upgrade_transfer|
disable|
reenable
reenable|
pre_provisioned_disable|
pre_provisioned_reenable
gam update cros <CrOSEntity> action <CrOSAction> [acknowledge_device_touch_requirement]
@@ -1314,7 +1331,7 @@ gam update cros <CrOSEntity> <CrOSAttribute>+
gam info cros <CrOSEntity> [nolists] [listlimit <Number>] [start <Date>] [end <Date>]
[basic|full|allfields] <CrOSFieldName>* [fields <CrOSFieldNameList>] [downloadfile latest|<Time>] [targetfolder <FilePath>]
gam print cros [todrive] [(query <QueryCrOS>)|(queries <QueryCrOSList>)] [limittoou <OrgUnitItem>]
gam print cros [todrive] [(query <QueryCrOS>)|(queries <QueryCrOSList>)] [limittoou|cros_ou|cros_ou_and_children <OrgUnitItem>]
[orderby <CrOSOrderByFieldName> [ascending|descending]] [nolists|<CrOSListFieldName>*] [listlimit <Number>] [start <Date>] [end <Date>]
[basic|full|allfields] <CrOSFieldName>* [fields <CrOSFieldNameList>] [sortheaders]
gam <CrOSTypeEntity> print
@@ -1322,7 +1339,7 @@ gam <CrOSTypeEntity> print
Summary of printing:
gam print cros
Prints a header row and deviceId for all CrOS devices.
gam <CrOSTypeEntity> print cros
gam <CrOSTypeEntity> print
Prints no header row and deviceId for specified CrOS devices.
gam print cros ... basic|full
Prints a header row and selected fields for specified CrOS devices.
@@ -1336,7 +1353,7 @@ One set of values for all <CrOSListFieldName> fields specified will be output on
The listlimit <Number> argument limits the number of repetitions to <Number>; if not specified or <Number> equals zero, there is no limit.
The start <Date> and end <Date> arguments constrain activeTimeRanges, cpuStatusReports, deviceFiles and systemRamFreeReports to fall within the specified <Dates>.
gam print crosactivity [todrive] [(query <QueryCrOS>)|(queries <QueryCrOSList>)] [limittoou <OrgUnitItem>]
gam print crosactivity [todrive] [(query <QueryCrOS>)|(queries <QueryCrOSList>)] [limittoou|cros_ou|cros_ou_and_children <OrgUnitItem>]
[recentusers] [timeranges] [both] [devicefiles] [all] [listlimit <Number>] [start <Date>] [end <Date>] [delimiter <Character>]
The basic column headers are: deviceId,annotatedAssetId,annotatedLocation,serialNumber,orgUnitPath.
@@ -1381,6 +1398,13 @@ gam print chromeappdevices [todrive]
[start <Date>] [end <Date>]
[orderby deviceid|machine]
gam print chromeaues [todrive]
[ou|org|orgunit <OrgUnitItem>]
[minauedate <Date>] [maxauedate <Date>]
gam print chromeneedsattn [todrive]
[ou|org|orgunit <OrgUnitItem>]
gam print chromeversions [todrive]
[ou|org|orgunit <OrgUnitItem>]
[start <Date>] [end <Date>] [recentfirst]
@@ -1495,7 +1519,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>
@@ -1537,6 +1561,44 @@ gam print group-members|groups-members [todrive]
[roles <GroupRoleList>] [membernames] [fields <MembersFieldNameList>]
[includederivedmembership]
<SSOProfileDisplayName> ::= <String>
<SSOProfileName> ::= id:inboundSamlSsoProfiles/<String>
<SSOProfileItem> ::= <SSOProfileDisplayName>|<SSOProfileName>
<SSOProfileItemList> ::= "<SSOProfileItem>(,<SSOProfileItem>)*"
gam create inboundssoprofile [name <SSOProfileDisplayName>]
[entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
[returnnameonly]
gam update inboundssoprofile <SSOProfileItem>
[entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
[returnnameonly]
gam delete inboundssoprofile <SSOProfileItem>
gam info inboundssoprofile <SSOProfileItem>
gam show inboundssoprofiles
gam print inboundssoprofiles [todrive]
<SSOCredentialsName> ::= [id:]inboundSamlSsoProfiles/<String>/idpCredentials/<String>
gam create inboundssocredential profile <SSOProfileItem>
(pemfile <FileName>)|(generatekey [keysize 1024|2048|4096]) [replaceolddest]
gam delete inboundssocredential <SSOCredentialsName>
gam show inboundssocredentials [profile|profiles <SSOProfileItemList>]
gam print inboundssocredentials [profile|profiles <SSOProfileItemList>] [todrive]
<SSOAssignmentSelector> ::=
groups/<String> |
group:<EmailAddress> |
orgunits/<String> |
orgunit:<OrgUnitPath>
gam create inboundssoassignment (group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)
(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled) [neverredirect]
gam update inboundssoassignment [(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)]
[(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled)] [neverredirect]
gam info inboundssoassignment <SSOAssignmentSelector>
gam show inboundssoassignments
gam print inboundssoassignments [todrive]
gam send userinvitation <EmailAddress>
gam cancel userinvitation <EmailAddress>
gam check userinvitation|isinvitable <EmailAddress>
@@ -1625,6 +1687,9 @@ gam show guardian|guardians [invitedguardian <EmailAddress>] [student <StudentIt
gam print guardian|guardians [todrive] [invitedguardian <EmailAddress>] [student <StudentItem>] [invitations [states <GuardianStateList>]] [<UserTypeEntity>]
gam cancel guardianinvitation|guardianinvitations <GuardianInvitationID> <StudentItem>
gam download storagebucket <URL>
gam copy storagebucket sourcebucket <URL> targetbucket <URL> [sourceprefix <String>] [targetprefix <String>]
gam create vaultexport|export matter <MatterItem> [name <name>] corpus <drive|mail|groups|hangouts_chat>
(accounts <EmailAddressList>) | (orgunit|ou <OrgUnitPath>) | (teamdrives <TeamDriveList>) | (rooms <ChatRoomList>) | everyone
[scope <all_data|held_data|unprocessed_data>]
@@ -1637,6 +1702,7 @@ gam delete export <MatterItem> <ExportItem>
gam info export <MatterItem> <ExportItem>
gam print exports [todrive] [matters <MatterItemList>]
gam download export <MatterItem> <ExportItem> [noverify] [noextract] [targetfolder <FilePath>]
gam copy export <MatterItem> <ExportItem> targetbucket <URL> [targetprefix <String>]
gam create vaulthold|hold corpus drive|groups|mail matter <MatterItem> [name <String>] [query <QueryVaultCorpus>]
[(accounts|groups|users <EmailItemList>) | (orgunit|ou <OrgUnit>)]
@@ -1839,7 +1905,7 @@ gam <UserTypeEntity> update teamdrive <TeamDriveID>|(name <TeamDriveName>) [asad
[(theme|themeid <String>) | ([customtheme <DriveFileID> <Float> <Float> <Float>] [color <ColorValue>])]
(<TeamDriveRestrictionsSubfieldName> <Boolean>)*
[hidden <Boolean>]
gam <UserTypeEntity> delete teamdrive <TeamDriveID>|(name <TeamDriveName>)
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]

View File

@@ -12,7 +12,7 @@
}
},
"basePath": "",
"baseUrl": "https://www.googleapis.com/admin/directory/v1.1beta1/customer/",
"baseUrl": "https://admin.googleapis.com/admin/directory/v1.1beta1/customer/",
"batchPath": "batch",
"canonicalName": "cbcm",
"discoveryVersion": "v1",

View File

@@ -113,10 +113,9 @@ case $gamos in
done
case $gamarch in
x86_64) gamfile="linux-x86_64-$useglibc.tar.xz";;
arm64|aarch64) gamfile="linux-aarch64-glibc2.28.tar.xz";;
arm|armv7l) gamfile="linux-armv7l-glibc2.28.tar.xz";;
arm64|aarch64) gamfile="linux-aarch64-$useglibc.tar.xz";;
*)
echo_red "ERROR: this installer currently only supports x86_64, arm and arm64 Linux. Looks like you're running on $gamarch. Exiting."
echo_red "ERROR: this installer currently only supports x86_64 and arm64 Linux. Looks like you're running on $gamarch. Exiting."
exit
esac
;;

View File

@@ -1,55 +1,110 @@
# -*- mode: python -*-
# -*- mode: python ; coding: utf-8 -*-
from os import getenv
from sys import platform
import sys
import importlib
from PyInstaller.utils.hooks import copy_metadata
# dynamically determine where httplib2/cacerts.txt lives
proot = os.path.dirname(importlib.import_module('httplib2').__file__)
extra_files = [(os.path.join(proot, 'cacerts.txt'), 'httplib2')]
extra_files = []
extra_files += copy_metadata('google-api-python-client')
extra_files += [('cbcm-v1.1beta1.json', '.')]
extra_files += [('contactdelegation-v1.json', '.')]
extra_files += [('admin-directory_v1.1beta1.json', '.')]
extra_files += [('roots.pem', '.')]
hidden_imports = [
'gam.auth.yubikey',
]
a = Analysis(['gam/__main__.py'],
hiddenimports=hidden_imports,
hookspath=None,
excludes=['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'],
datas=extra_files,
runtime_hooks=None)
a = Analysis(
['gam/__main__.py'],
pathex=[],
binaries=[],
datas=extra_files,
hiddenimports=hidden_imports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=None,
noarchive=False,
)
for d in a.datas:
if 'pyconfig' in d[0]:
a.datas.remove(d)
break
pyz = PYZ(a.pure)
if sys.platform == "darwin":
target_arch="universal2"
pyz = PYZ(a.pure,
a.zipped_data,
cipher=None)
# requires Python 3.10+ but no one should be compiling
# GAM with older versions anyway
match platform:
case "darwin":
target_arch = "universal2"
strip = True
case "win32":
target_arch = None
strip = False
case _:
target_arch = None
strip = True
name = 'gam'
debug = False
bootloader_ignore_signals = False
upx = False
console = True
disable_windowed_traceback = False
argv_emulation = False
codesign_identity = None
entitlements_file = None
if getenv('PYINSTALLER_BUILD_ONEFILE') == 'yes':
# Build one file
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
[],
name=name,
debug=debug,
bootloader_ignore_signals=bootloader_ignore_signals,
strip=strip,
upx=upx,
console=console,
disable_windowed_traceback=disable_windowed_traceback,
argv_emulation=argv_emulation,
target_arch=target_arch,
codesign_identity=codesign_identity,
entitlements_file=entitlements_file,
)
else:
target_arch=None
# Build one folder
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name=name,
debug=debug,
bootloader_ignore_signals=bootloader_ignore_signals,
strip=strip,
upx=upx,
console=console,
disable_windowed_traceback=disable_windowed_traceback,
argv_emulation=argv_emulation,
target_arch=target_arch,
codesign_identity=codesign_identity,
entitlements_file=entitlements_file,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=strip,
upx=upx,
upx_exclude=[],
name=name,
)
# use strip on all non-Windows platforms
strip = not sys.platform == 'win32'
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name='gam',
debug=False,
strip=strip,
upx=False,
target_arch=target_arch,
console=True)

View File

@@ -55,6 +55,9 @@
<Component Id="gamcommands_txt" Guid="58ff9c45-a7c9-4e22-8845-a9a92610c1f3">
<File Name="gamcommands.txt" KeyPath="yes" />
</Component>
<Component Id="roots_pem" Guid="18ff9c45-a3c9-4e22-8445-a8a92610c1f3">
<File Name="roots.pem" KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>

File diff suppressed because it is too large Load Diff

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

@@ -9,6 +9,7 @@ import gam
from gam import utils
from gam.auth import oauth
from gam.auth import signjwt
from gam.var import _FN_OAUTH2_TXT
from gam.var import _FN_OAUTH2SERVICE_JSON
from gam.var import GC_OAUTH2_TXT
@@ -40,7 +41,7 @@ def get_admin_credentials(api=None):
with open(credential_file) as f:
creds_data = json.load(f)
# Validate that enable DASA matches content of authorization file
if GC_Values[GC_ENABLE_DASA] and 'private_key_id' in creds_data:
if GC_Values[GC_ENABLE_DASA] and 'key_type' in creds_data:
audience = f'https://{api}.googleapis.com/'
key_type = creds_data.get('key_type', 'default')
if key_type == 'default':
@@ -51,6 +52,11 @@ def get_admin_credentials(api=None):
return JWTCredentials._from_signer_and_info(yksigner,
creds_data,
audience=audience)
elif key_type == 'signjwt':
sjsigner = signjwt.SignJwt(creds_data)
return signjwt.JWTCredentials._from_signer_and_info(sjsigner,
creds_data,
audience=audience)
elif not GC_Values[GC_ENABLE_DASA] and 'token' in creds_data:
return oauth.Credentials.from_credentials_file(credential_file)
else:

View File

@@ -24,7 +24,10 @@ from gam import controlflow
from gam import display
from gam import fileutils
from gam import transport
from gam.var import GM_Globals, GM_WINDOWS
from gam.var import (GC_CA_FILE,
GC_Values,
GM_Globals,
GM_WINDOWS)
from gam import utils
@@ -269,6 +272,7 @@ class Credentials(google.oauth2.credentials.Credentials):
access_type='offline',
login_hint=None,
filename=None,
open_browser=True,
use_console_flow=False):
"""Runs an OAuth Flow from client secrets to generate credentials.
@@ -288,8 +292,11 @@ class Credentials(google.oauth2.credentials.Credentials):
login_hint: String, The email address that will be displayed on the Google
login page as a hint for the user to login to the correct account.
filename: String, the path to a file to use to save the credentials.
use_console_flow: Boolean, True if the authentication flow should be run
strictly from a console; False to launch a browser for authentication.
use_console_flow: OBSOLETE: Boolean, True if the authentication flow
should be run strictly from a console; False to launch a browser
for authentication.
open_browser: Boolean: whether or not GAM should try to open the browser
automatically.
Returns:
Credentials
@@ -309,12 +316,11 @@ class Credentials(google.oauth2.credentials.Credentials):
flow = _ShortURLFlow.from_client_config(client_config,
scopes,
autogenerate_code_verifier=True)
flow_kwargs = {'access_type': access_type}
flow_kwargs = {'access_type': access_type,
'open_browser': open_browser}
if login_hint:
flow_kwargs['login_hint'] = login_hint
flow.run_dual(use_console_flow,
**flow_kwargs)
flow.run_dual(**flow_kwargs)
return cls.from_google_oauth2_credentials(flow.credentials,
filename=filename)
@@ -325,6 +331,7 @@ class Credentials(google.oauth2.credentials.Credentials):
access_type='offline',
login_hint=None,
credentials_file=None,
open_browser=True,
use_console_flow=False):
"""Runs an OAuth Flow from secrets stored on disk to generate credentials.
@@ -345,8 +352,11 @@ class Credentials(google.oauth2.credentials.Credentials):
login page as a hint for the user to login to the correct account.
credentials_file: String, the path to a file to use to save the
credentials.
use_console_flow: Boolean, True if the authentication flow should be run
strictly from a console; False to launch a browser for authentication.
use_console_flow: OBSOLETE: Boolean, True if the authentication flow
should be run strictly from a console; False to launch a browser for
authentication.
open_browser: Boolean, whether or not GAM should try to open the browser
directly.
Raises:
InvalidClientSecretsFileError: If the client secrets file cannot be
@@ -375,14 +385,13 @@ class Credentials(google.oauth2.credentials.Credentials):
raise InvalidClientSecretsFileFormatError(
f'Could not extract Client ID or Client Secret from file {client_secrets_file}'
)
return cls.from_client_secrets(client_id,
client_secret,
scopes,
access_type=access_type,
login_hint=login_hint,
filename=credentials_file,
use_console_flow=use_console_flow)
open_browser=open_browser)
def _fetch_id_token_data(self):
"""Fetches verification details from Google for the OAuth2.0 token.
@@ -479,7 +488,11 @@ class Credentials(google.oauth2.credentials.Credentials):
def _locked_refresh(self, request):
"""Refreshes the credential's access token while the file lock is held."""
assert self._lock.is_locked
super().refresh(request)
try:
super().refresh(request)
except google.auth.exceptions.RefreshError as e:
controlflow.system_error_exit(9, str(e))
def write(self):
"""Writes credentials to disk."""
@@ -592,7 +605,6 @@ class _ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
def run_dual(self,
use_console_flow,
authorization_prompt_message='',
console_prompt_message='',
web_success_message='',
@@ -602,7 +614,7 @@ class _ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
mgr = multiprocessing.Manager()
d = mgr.dict()
d['trailing_slash'] = redirect_uri_trailing_slash
d['open_browser'] = use_console_flow
d['open_browser'] = open_browser
http_client = multiprocessing.Process(target=_wait_for_http_client,
args=(d,))
user_input = multiprocessing.Process(target=_wait_for_user_input,
@@ -633,7 +645,10 @@ class _ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
parsed_params = parse_qs(parsed_url.query)
code = parsed_params.get('code', [None])[0]
try:
self.fetch_token(code=code)
fetch_args = {'code': code}
if GC_Values.get(GC_CA_FILE):
fetch_args['verify'] = GC_Values.get(GC_CA_FILE)
self.fetch_token(**fetch_args)
break
except Exception as e:
if not userInput:

89
src/gam/auth/signjwt.py Normal file
View File

@@ -0,0 +1,89 @@
''' Use Google Application Default Credentials '''
import datetime
import json
import google.auth
from google.auth._helpers import datetime_to_secs, scopes_to_string, utcnow
import google.oauth2.service_account
import gam
from gam import controlflow
from gam import gapi
from gam import transport
from gam.var import GM_Globals, GM_CACHE_DIR
_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds
_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token"
class JWTCredentials(google.auth.jwt.Credentials):
''' Class used for DASA '''
def _make_jwt(self):
now = utcnow()
lifetime = datetime.timedelta(seconds=self._token_lifetime)
expiry = now + lifetime
payload = {
"iss": self._issuer,
"sub": self._subject,
"iat": datetime_to_secs(now),
"exp": datetime_to_secs(expiry),
}
if self._audience:
payload["aud"] = self._audience
payload.update(self._additional_claims)
jwt = self._signer.sign(payload)
return jwt, expiry
class Credentials(google.oauth2.service_account.Credentials):
''' Class used for DwD '''
def _make_authorization_grant_assertion(self):
now = utcnow()
lifetime = datetime.timedelta(seconds=_DEFAULT_TOKEN_LIFETIME_SECS)
expiry = now + lifetime
payload = {
"iat": datetime_to_secs(now),
"exp": datetime_to_secs(expiry),
"iss": self._service_account_email,
"aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT,
"scope": scopes_to_string(self._scopes or ()),
}
payload.update(self._additional_claims)
# The subject can be a user email for domain-wide delegation.
if self._subject:
payload.setdefault("sub", self._subject)
token = self._signer(payload)
return token
class SignJwt(google.auth.crypt.Signer):
''' Signer class for SignJWT '''
def __init__(self, service_account_info):
self.service_account_email = service_account_info['client_email']
self.name = f'projects/-/serviceAccounts/{self.service_account_email}'
self._key_id = None
@property # type: ignore
def key_id(self):
return self._key_id
def sign(self, message):
''' Call IAM Credentials SignJWT API to get our signed JWT '''
try:
credentials, _ = google.auth.default()
except google.auth.exceptions.DefaultCredentialsError as e:
controlflow.system_error_exit(2, e)
httpObj = transport.AuthorizedHttp(
credentials,
transport.create_http(cache=GM_Globals[GM_CACHE_DIR]))
iamc = gam.getService('iamcredentials', httpObj)
response = gapi.call(iamc.projects().serviceAccounts(),
'signJwt',
name=self.name,
body={'payload': json.dumps(message)})
signed_jwt = response.get('signedJwt')
return signed_jwt

View File

@@ -8,7 +8,7 @@ from threading import Timer
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from smartcard.Exceptions import CardConnectionException
from ykman.device import connect_to_device
from ykman.device import list_all_devices
from ykman.piv import generate_self_signed_certificate, \
generate_chuid
from yubikit.piv import DEFAULT_MANAGEMENT_KEY, \
@@ -20,11 +20,14 @@ from yubikit.piv import DEFAULT_MANAGEMENT_KEY, \
OBJECT_ID, \
SLOT, \
TOUCH_POLICY
from yubikit.core.smartcard import ApduError
from yubikit.core.smartcard import ApduError, \
SmartCardConnection
from gam import controlflow
class YubiKey():
def __init__(self, service_account_info=None):
self.key_type = None
self.slot = None
@@ -46,12 +49,16 @@ class YubiKey():
self.pin = service_account_info.get('yubikey_pin')
self.key_id = service_account_info.get('private_key_id')
def _connect(self):
try:
conn, _, _ = connect_to_device(self.serial_number)
devices = list_all_devices()
for (device, info) in devices:
if info.serial == self.serial_number:
return device.open_connection(SmartCardConnection)
except CardConnectionException as err:
controlflow.system_error_exit(9, f'YubiKey - {err}')
return conn
def get_certificate(self):
try:
@@ -79,11 +86,25 @@ class YubiKey():
def get_serial_number(self):
try:
_, _, info = connect_to_device(self.serial_number)
return info.serial
devices = list_all_devices()
if not devices:
msg = f'Could not find any YubiKey'
controlflow.system_error_exit(3, msg)
if self.serial_number:
for (device, info) in devices:
if info.serial == self.serial_number:
return info.serial
msg = f'Could not find YubiKey with serial {self.serial_number}'
controlflow.system_error_exit(3, msg)
if len(devices) > 1:
serials = ', '.join([str(info.serial) for (_, info) in devices])
msg = f'Multiple YubiKeys connected. Specify yubikey_serial_number and one of {serials}'
controlflow.system_error_exit(4, msg)
return devices[0][1].serial
except ValueError as err:
controlflow.system_error_exit(9, f'YubiKey - {err}')
def reset_piv(self):
'''Resets YubiKey PIV app and generates new key for GAM to use.'''
reply = str(input('This will wipe all PIV keys and configuration from your YubiKey. Are you sure? (y/N) ').lower().strip())
@@ -95,7 +116,9 @@ class YubiKey():
piv = PivSession(conn)
piv.reset()
rnd = SystemRandom()
pin_puk_chars = string.ascii_letters + string.digits + string.punctuation
pin_puk_chars = string.ascii_letters + \
string.digits + \
string.punctuation
new_puk = ''.join(rnd.choice(pin_puk_chars) for _ in range(8))
new_pin = ''.join(rnd.choice(pin_puk_chars) for _ in range(8))
piv.change_puk('12345678', new_puk)
@@ -155,3 +178,4 @@ class YubiKey():
if 'mplock' in globals():
mplock.release()
return signed

View File

@@ -34,7 +34,7 @@ def missing_argument_exit(argument, command):
"""Indicate that the argument is missing for the command.
Args:
argument: the missingagrument
argument: the missing argument
command: the base GAM command
"""
system_error_exit(2, f'missing argument {argument} for "{command}"')

View File

@@ -1,6 +1,8 @@
"""Methods related to execution of GAPI requests."""
import os.path
import sys
from tempfile import TemporaryDirectory
import googleapiclient.errors
import google.auth.exceptions
@@ -10,7 +12,8 @@ from gam import controlflow
from gam import display
from gam.gapi import errors
from gam import transport
from gam.var import (GM_Globals, GM_CURRENT_API_SCOPES, GM_CURRENT_API_USER,
from gam.var import (GC_Values, GM_Globals,
GM_CURRENT_API_SCOPES, GM_CURRENT_API_USER,
GM_EXTRA_ARGS_DICT, GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID,
MAX_RESULTS_API_EXCEPTIONS, MESSAGE_API_ACCESS_CONFIG,
MESSAGE_API_ACCESS_DENIED, MESSAGE_SERVICE_NOT_APPLICABLE)
@@ -238,8 +241,13 @@ def process_page(page, items, all_items, total_items, page_message, message_attr
page_items = page.get(items, [])
num_page_items = len(page_items)
total_items += num_page_items
if all_items is not None:
if type(all_items) is list:
all_items.extend(page_items)
elif all_items is not None:
i = len(all_items)
for item in page_items:
all_items[str(i)] = item
i += 1
else:
page_token = None
num_page_items = 0
@@ -273,6 +281,7 @@ def finalize_page_message(page_message):
sys.stderr.write('\r\n')
sys.stderr.flush()
def get_all_pages(service,
function,
items='items',
@@ -341,13 +350,14 @@ def get_all_pages(service,
page_token, total_items = process_page(page, items, all_items, total_items, page_message, message_attribute)
if not page_token:
finalize_page_message(page_message)
if type(all_items) is not list:
all_items = all_items.values()
return all_items
if page_args_in_body:
kwargs['body']['pageToken'] = page_token
else:
kwargs['pageToken'] = page_token
# TODO: Make this private once all execution related items that use this method
# have been brought into this file
def handle_oauth_token_error(e, soft_errors):

View File

@@ -219,7 +219,7 @@ def printShowCrosTelemetry(mode):
i = 3
if mode == 'info':
if i >= len(sys.argv):
controlflow.system_error_exit(3, f'<SerialNumber> required for "gam info crostelemetry"')
controlflow.system_error_exit(3, '<SerialNumber> required for "gam info crostelemetry"')
filter_ = f'serialNumber={sys.argv[i]}'
i += 1
mode = 'show'
@@ -286,7 +286,8 @@ def printShowCrosTelemetry(mode):
device['storageInfo']['percentDiskUsed'] = 100 - device['storageInfo']['percentDiskFree']
for cpuStatusReport in device.get('cpuStatusReport', []):
for tempInfo in cpuStatusReport.pop('cpuTemperatureInfo', []):
cpuStatusReport[f"cpuTemperatureInfo.{tempInfo['label'].strip()}"] = tempInfo['temperatureCelsius']
if 'temperatureCelsius' in tempInfo:
cpuStatusReport[f"cpuTemperatureInfo.{tempInfo['label'].strip()}"] = tempInfo['temperatureCelsius']
if showOrgUnitPath:
orgUnitId = device.get('orgUnitId')
if orgUnitId not in orgUnitIdPathMap:
@@ -306,6 +307,97 @@ def printShowCrosTelemetry(mode):
display.write_csv_file(csvRows, titles, 'Telemetry Devices', todrive)
CHROME_AUES_TITLES = [
'model', 'count', 'aueMonth', 'aueYear', 'expired'
]
def printAUEs():
cm = build()
customer = _get_customerid()
todrive = False
titles = CHROME_AUES_TITLES
csvRows = []
orgunit = None
minAueDate = None
maxAueDate = None
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'todrive':
todrive = True
i += 1
elif myarg in ['ou', 'org', 'orgunit']:
orgunit = _get_orgunit(sys.argv[i+1])
i += 2
elif myarg == 'minauedate':
minAueDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT)
i += 2
elif myarg == 'maxauedate':
maxAueDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT)
i += 2
else:
msg = f'{myarg} is not a valid argument to "gam print chromeaues"'
controlflow.system_error_exit(3, msg)
if orgunit:
orgUnitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit, None)
query = f'orgUnitPath={orgUnitPath}'
titles.append('orgUnitPath')
else:
orgUnitPath = '/'
query = None
gam.printGettingAllItems('Chrome Auto Update Expirations', query)
aues = gapi.call(cm.customers().reports(),
'countChromeDevicesReachingAutoExpirationDate',
customer=customer, orgUnitId=orgunit,
minAueDate=minAueDate, maxAueDate=maxAueDate).get('deviceAueCountReports', [])
for aue in sorted(aues, key=lambda k: k.get('model', 'Unknown')):
if orgunit:
aue['orgUnitPath'] = orgUnitPath
csvRows.append(aue)
display.write_csv_file(csvRows, titles, 'Chrome AUEs', todrive)
CHROME_NEEDSATTN_TITLES = [
'noRecentPolicySyncCount', 'noRecentUserActivityCount', 'pendingUpdate',
'osVersionNotCompliantCount', 'unsupportedPolicyCount'
]
def printNeedsAttn():
cm = build()
customer = _get_customerid()
todrive = False
titles = CHROME_NEEDSATTN_TITLES[:]
csvRows = []
orgunit = None
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'todrive':
todrive = True
i += 1
elif myarg in ['ou', 'org', 'orgunit']:
orgunit = _get_orgunit(sys.argv[i+1])
i += 2
else:
msg = f'{myarg} is not a valid argument to "gam print chromeneedsattn"'
controlflow.system_error_exit(3, msg)
if orgunit:
orgUnitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit, None)
query = f'orgUnitPath={orgUnitPath}'
titles.append('orgUnitPath')
else:
orgUnitPath = '/'
query = None
gam.printGettingAllItems('Chrome Devices Needing Attention', query)
result = gapi.call(cm.customers().reports(),
'countChromeDevicesThatNeedAttention',
customer=customer, orgUnitId=orgunit, readMask=','.join(CHROME_NEEDSATTN_TITLES))
for field in CHROME_NEEDSATTN_TITLES:
result.setdefault(field, 0)
if orgunit:
result['orgUnitPath'] = orgUnitPath
csvRows.append(result)
display.write_csv_file(csvRows, titles, 'Chrome Devices Needing Attention', todrive)
CHROME_VERSIONS_TITLES = [
'version', 'count', 'channel', 'deviceOsVersion', 'system'
]
@@ -319,6 +411,7 @@ def printVersions():
startDate = None
endDate = None
pfilter = None
query = None
reverse = False
i = 3
while i < len(sys.argv):
@@ -349,12 +442,18 @@ def printVersions():
else:
pfilter = ''
pfilter += f'last_active_date>={startDate}'
query = pfilter
if orgunit:
orgUnitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit, None)
if query:
query += ' AND '
else:
query = ''
query += f'orgUnitPath={orgUnitPath}'
titles.append('orgUnitPath')
else:
orgUnitPath = '/'
gam.printGettingAllItems('Chrome Versions', pfilter)
gam.printGettingAllItems('Chrome Versions', query)
page_message = gapi.got_total_items_msg('Chrome Versions', '...\n')
versions = gapi.get_all_pages(cm.customers().reports(),
'countChromeVersions',

View File

@@ -167,7 +167,7 @@ def build_schemas(svc=None, sfilter=None):
for an_enum in schema['definition']['enumType']:
if an_enum['name'] == type_name:
setting_dict['enums'] = [enum['name'] for enum in an_enum['value']]
setting_dict['enum_prefix'] = utils.commonprefix(setting_dict['enums'])
setting_dict['enum_prefix'] = utils.commonprefix(setting_dict['enums'], True)
prefix_len = len(setting_dict['enum_prefix'])
setting_dict['enums'] = [enum[prefix_len:] for enum \
in setting_dict['enums'] \
@@ -408,7 +408,7 @@ def update_policy():
f'{expected_enums}, got {value}'
controlflow.system_error_exit(8, msg)
elif vtype in ['TYPE_LIST']:
value = value.split(',')
value = value.split(',') if value else []
if myarg == 'chrome.users.chromebrowserupdates' and \
cased_field == 'targetVersionPrefixSetting':
mg = re.compile(r'^([a-z]+)-(\d+)$').match(value)

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

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

View File

@@ -217,6 +217,7 @@ def print_():
gapi_directory_customer.setTrueCustomerId()
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
usemember = None
query = None
memberDelimiter = '\n'
todrive = False
titles = []
@@ -235,6 +236,9 @@ def print_():
elif myarg == 'delimiter':
memberDelimiter = sys.argv[i + 1]
i += 2
elif myarg == 'query':
query = sys.argv[i + 1]
i += 2
elif myarg == 'sortheaders':
sortHeaders = True
i += 1
@@ -314,14 +318,20 @@ def print_():
if entity['relationType'] == 'DIRECT':
entityList.append(gapi.call(ci.groups(), 'get', name=entity['group']))
else:
if query:
method = 'search'
kwargs = {'query': query}
else:
method = 'list'
kwargs = {'parent': parent}
entityList = gapi.get_all_pages(ci.groups(),
'list',
method,
'groups',
page_message=page_message,
message_attribute=['groupKey', 'id'],
parent=parent,
view='FULL',
pageSize=500)
pageSize=500,
**kwargs)
i = 0
count = len(entityList)
for groupEntity in entityList:
@@ -866,6 +876,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': [{
@@ -928,6 +945,12 @@ def group_email_to_id(ci, group, i=0, count=0):
return None
def group_id_to_email(ci, group_id):
return gapi.call(ci.groups(),
'get',
fields='groupKey/id',
name=group_id).get('groupKey', {}).get('id')
def membership_email_to_id(ci, parent, membership, i=0, count=0):
membership = gam.normalizeEmailAddressOrUID(membership)
try:

View File

@@ -0,0 +1,538 @@
"""Methods related to Cloud Identity Inbound (Google as SP) SAML SSO"""
from datetime import datetime
import re
import sys
import dateutil.parser
import googleapiclient
import gam
from gam.var import GC_CUSTOMER_ID, GC_Values, MY_CUSTOMER
from gam import controlflow
from gam import display
from gam import fileutils
from gam import gapi
from gam import utils
from gam.gapi import errors as gapi_errors
from gam.gapi import cloudidentity as gapi_cloudidentity
from gam.gapi import directory as gapi_directory
from gam.gapi.cloudidentity import groups as gapi_cloudidentity_groups
from gam.gapi.directory import orgunits as gapi_directory_orgunits
'''returns customer in the format inboundsso requires'''
def get_sso_customer():
customer = GC_Values[GC_CUSTOMER_ID]
return f'customers/{customer}'
'''returns org unit in the format inboundsso requires'''
def get_orgunit_id(orgunit):
ou_id = gapi_directory_orgunits.getOrgUnitId(orgunit)[1]
if ou_id.startswith('id:'):
ou_id = ou_id[3:]
return f'orgUnits/{ou_id}'
'''build Cloud Identity API'''
def build():
return gapi_cloudidentity.build('cloudidentity_beta')
'''parse cmd for profile create/update'''
def parse_profile(body, i):
name_only = False
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'name':
body['displayName'] = sys.argv[i+1]
i += 2
elif myarg == 'entityid':
body.setdefault('idpConfig', {})['entityId'] = sys.argv[i+1]
i += 2
elif myarg == 'returnnameonly':
name_only = True
i += 1
elif myarg == 'loginurl':
body.setdefault('idpConfig', {})['singleSignOnServiceUri'] = sys.argv[i+1]
i += 2
elif myarg == 'logouturl':
body.setdefault('idpConfig', {})['logoutRedirectUri'] = sys.argv[i+1]
i += 2
elif myarg == 'changepasswordurl':
body.setdefault('idpConfig', {})['changePasswordUri'] = sys.argv[i+1]
i += 2
else:
controlflow.invalid_argument_exit(myarg, 'gam create/update inboundssoprofile')
return (name_only, body)
'''convert profile nice names to unique ID'''
def profile_displayname_to_name(displayName, ci=None):
if displayName.lower().startswith('id:') or displayName.lower().startswith('uid:'):
displayName = displayName.split(':', 1)[1]
if not displayName.startswith('inboundSamlSsoProfiles/'):
displayName = f'inboundSamlSsoProfiles/{displayName}'
return displayName
if not ci:
ci = build()
customer = get_sso_customer()
_filter = f'customer=="{customer}"'
profiles = gapi.get_all_pages(ci.inboundSamlSsoProfiles(),
'list',
'inboundSamlSsoProfiles',
filter=_filter)
matches = []
for profile in profiles:
if displayName.lower() == profile.get('displayName', '').lower():
matches.append(profile)
if len(matches) == 1:
return matches[0]['name']
if len(matches) == 0:
controlflow.system_error_exit(3, f'No Inbound SSO profile matches the name {displayName}')
else:
err_text = f'Multiple profiles match {displayName}:\n\n'
for m in matches:
err_text += f' {m["name"]} {m["displayName"]}\n'
controlflow.system_error_exit(3, err_text)
'''get an assignment based on target'''
def assignment_by_target(target, ci=None):
if not ci:
ci = build()
group_pattern = r'^groups/[^/]+$'
ou_pattern = r'^orgUnits/[^/]+$'
if re.match(group_pattern, target):
target_type = 'targetGroup'
elif re.match(ou_pattern, target):
target_type = 'targetOrgUnit'
elif target.lower().startswith('group:'):
target_type = 'targetGroup'
group_email = target[6:]
target = gapi_cloudidentity_groups.group_email_to_id(
ci,
group_email)
elif target.lower().startswith('orgunit:'):
target_type = 'targetOrgUnit'
ou_name = target[8:]
target = get_orgunit_id(ou_name)
else:
controlflow.system_error_exit(3, 'assignments should be prefixed with ' +
'group:, groups/, orgunit: or orgunits/')
customer = get_sso_customer()
_filter = f'customer=="{customer}"'
assignments = gapi.get_all_pages(ci.inboundSsoAssignments(),
'list',
'inboundSsoAssignments',
filter=_filter)
for assignment in assignments:
if target_type in assignment and assignment[target_type] == target:
return assignment
controlflow.system_error_exit(3, f'No SSO profile assigned to {target_type} {target}')
'''gam create inboundssoprofile'''
def create_profile():
ci = build()
body = {
'customer': get_sso_customer(),
'displayName': 'SSO Profile'
}
name_only, body = parse_profile(body, 3)
result = gapi.call(ci.inboundSamlSsoProfiles(),
'create',
body=body)
if result.get('done'):
if name_only:
print(result['response']['name'])
else:
print(f'Created profile {result["response"]["name"]}')
display.print_json(result['response'])
else:
controlflow.system_error_exit(3, 'Create did not finish {result}')
'''gam print inboundssoprofiles'''
def print_show_profiles(action='print'):
customer = get_sso_customer()
_filter = f'customer=="{customer}"'
ci = build()
todrive = False
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(myarg, f'gam {action} inboundssoprofiles')
profiles = gapi.get_all_pages(ci.inboundSamlSsoProfiles(),
'list',
'inboundSamlSsoProfiles',
filter=_filter)
if action == 'show':
for profile in profiles:
display.print_json(profile)
print()
elif action == 'print':
csv_rows = []
titles = []
for profile in profiles:
row = utils.flatten_json(profile)
for item in row:
if item not in titles:
titles.append(item)
csv_rows.append(row)
display.write_csv_file(csv_rows,
titles,
'Inbound SSO Profiles',
todrive)
'''gam update inboundssoprofile'''
def update_profile():
ci = build()
name = profile_displayname_to_name(sys.argv[3], ci)
body = {}
name_only, body = parse_profile(body, 4)
updateMask = ','.join(body.keys())
result = gapi.call(ci.inboundSamlSsoProfiles(),
'patch',
name=name,
updateMask=updateMask,
body=body)
if name_only:
print(result['response']['name'])
else:
display.print_json(result)
'''gam info inboundssoprofile'''
def info_profile(return_only=False, displayName=None, ci=None):
if not ci:
ci = build()
if not displayName:
displayName = sys.argv[3]
name = profile_displayname_to_name(displayName, ci)
result = gapi.call(ci.inboundSamlSsoProfiles(),
'get',
name=name)
if return_only:
return result
display.print_json(result)
'''gam delete inboundssoprofile'''
def delete_profile():
ci = build()
name = profile_displayname_to_name(sys.argv[3], ci)
result = gapi.call(ci.inboundSamlSsoProfiles(),
'delete',
name=name)
if result.get('done'):
print(f'Deleted profile {name}.')
else:
controlflow.system_error_exit(3, 'Delete did not finish: {result}')
'''gam create inboundssocredentials'''
def create_credentials():
allowed_sizes = [1024, 2048, 4096]
ci = build()
parent = None
generate_key = False
key_size = 2048
pemData = None
replace_oldest = False
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'profile':
parent = sys.argv[i+1]
parent = profile_displayname_to_name(parent, ci)
i += 2
elif myarg == 'pemfile':
pemfile = sys.argv[i+1]
pemData = fileutils.read_file(pemfile)
i += 2
elif myarg == 'generatekey':
generate_key = True
i += 1
elif myarg == 'replaceoldest':
replace_oldest = True
i += 1
elif myarg == 'keysize':
key_size = int(sys.argv[i+1])
if key_size not in allowed_sizes:
controlflow.expected_argument_exit('key_size',
allowed_sizes,
key_size)
i += 2
else:
controlflow.invalid_argument_exit(myarg, 'gam create inboundssocredential')
if not parent:
controlflow.missing_argument_exit('profile', 'gam create inboundssocredential')
if replace_oldest:
fields='nextPageToken,idpCredentials(name,updateTime)'
current_creds = gapi.get_all_pages(
ci.inboundSamlSsoProfiles().idpCredentials(),
'list',
'idpCredentials',
parent=parent,
fields=fields)
if len(current_creds) == 2:
oldest_key = min(current_creds,
key=lambda x:x['updateTime'])
print(' deleting older key...')
delete_credentials(ci=ci,
name=oldest_key['name'])
else:
print(' profile has {len(current_creds)} credentials. We only replace if there are 2.')
if generate_key:
privKey, pemData = gam._generatePrivateKeyAndPublicCert('GAM',
key_size,
b64enc_pub=False)
timestamp = datetime.now().strftime('%Y%m%d-%I%M%S')
priv_file = f'privatekey-{timestamp}.pem'
pub_file = f'publiccert-{timestamp}.pem'
fileutils.write_file(priv_file, privKey)
print(f' Wrote private key data to {priv_file}')
fileutils.write_file(pub_file, pemData)
print(f' Wrote public certificate to {pub_file}')
if not pemData:
controlflow.system_error_exit(3, 'You must either specify "pemfile <filename>" or "generate_key"')
body = {
'pemData': pemData,
}
result = gapi.call(ci.inboundSamlSsoProfiles().idpCredentials(),
'add',
parent=parent,
body=body)
if result.get('done'):
print(f'Created credential {result["response"]["name"]}')
display.print_json(result['response'])
else:
controlflow.system_error_exit(3, 'Create did not finish {result}')
'''gam delete inboundssocredential'''
def delete_credentials(ci=None, name=None):
if not ci:
ci = build()
if not name:
name = sys.argv[3]
result = gapi.call(ci.inboundSamlSsoProfiles().idpCredentials(),
'delete',
name=name)
if result.get('done'):
print(f'Deleted credential {name}')
else:
controlflow.system_error_exit(3, 'Delete did not finish {result}')
'''gam print inboundssocredentials'''
def print_show_credentials(action='print'):
ci = build()
todrive = False
i = 3
profiles = []
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['profile', 'profiles']:
profiles = [profile_displayname_to_name(profile, ci)
for profile in sys.argv[i+1].split(',')]
i += 2
elif myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(myarg, f'gam {action} inboundssocredentials')
if not profiles:
customer = get_sso_customer()
_filter = f'customer=="{customer}"'
profiles = gapi.get_all_pages(ci.inboundSamlSsoProfiles(),
'list',
'inboundSamlSsoProfiles',
fields='inboundSamlSsoProfiles/name',
filter=_filter)
profiles = [p['name'] for p in profiles]
if action == 'print':
titles = []
csv_rows = []
credentials = []
for profile in profiles:
results = gapi.get_all_pages(ci.inboundSamlSsoProfiles().idpCredentials(),
'list',
'idpCredentials',
parent=profile)
credentials.extend(results)
if action == 'show':
for c in credentials:
display.print_json(c)
print()
elif action == 'print':
for c in credentials:
csv_row = utils.flatten_json(c)
for item in csv_row:
if item not in titles:
titles.append(item)
csv_rows.append(csv_row)
display.write_csv_file(csv_rows,
titles,
'Inbound SSO Credentials',
todrive)
'''parse command for create/update inboundssoassignment'''
def parse_assignment(body, i, ci):
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'rank':
body['rank'] = int(sys.argv[i+1])
i += 2
elif myarg == 'mode':
mode_choices = \
gapi.get_enum_values_minus_unspecified(
ci._rootDesc['schemas']['InboundSsoAssignment']['properties']['ssoMode']['enum'])
body['ssoMode'] = sys.argv[i+1].upper()
if body['ssoMode'] not in mode_choices:
controlflow.expected_argument_exit('mode',
', '.join(mode_choices),
sys.argv[i+1])
i += 2
elif myarg == 'profile':
profile_name = profile_displayname_to_name(
sys.argv[i+1],
ci)
body['samlSsoInfo'] = {
'inboundSamlSsoProfile': profile_name
}
i += 2
elif myarg == 'neverredirect':
body['signInBehavior'] = {
'redirectCondition': 'NEVER'
}
i += 1
elif myarg == 'group':
group = sys.argv[i+1]
body['targetGroup'] = gapi_cloudidentity_groups.group_email_to_id(
ci,
group)
i += 2
elif myarg in ['ou', 'org', 'orgunit']:
body['targetOrgUnit'] = get_orgunit_id(sys.argv[i+1])
i += 2
else:
controlflow.invalid_argument_exit(myarg, 'gam create/update inboundssoassignment')
return body
def update_assignment_target_names(assignment, ci, cd):
if 'targetGroup' in assignment:
assignment['targetGroupEmail'] = \
gapi_cloudidentity_groups.group_id_to_email(ci,
assignment['targetGroup'])
elif 'targetOrgUnit' in assignment:
ou_id = assignment['targetOrgUnit'].split('/')[1]
assignment['targetOrgUnitPath'] = \
gapi_directory_orgunits.orgunit_from_orgunitid(f'id:{ou_id}', cd)
'''gam create inboundssoassignment'''
def create_assignment():
ci = build()
cd = gapi_directory.build()
body = {
'customer': get_sso_customer(),
}
body = parse_assignment(body, 3, ci)
result = gapi.call(ci.inboundSsoAssignments(),
'create',
body=body)
if result.get('done'):
print(f'Created assignment {result["response"]["name"]}')
update_assignment_target_names(result['response'], ci, cd)
display.print_json(result['response'])
else:
controlflow.system_error_exit(3, 'Create did not finish {result}')
def get_assignment_name(name):
if name.startswith('id:') or name.startswith('uid:'):
name = name.split(':', 1)[1]
if not name.startswith('inboundSsoAssignments/'):
name = f'inboundSsoAssignments/{name}'
return name
'''gam update inboundssoassignment'''
def update_assignment():
ci = build()
cd = gapi_directory.build()
name = get_assignment_name(sys.argv[3])
body = parse_assignment({}, 4, ci)
updateMask = ','.join(list(body.keys()))
result = gapi.call(ci.inboundSsoAssignments(),
'patch',
name=name,
updateMask=updateMask,
body=body)
if result.get('done'):
print(f'Updated assignment {result["response"]["name"]}')
update_assignment_target_names(result['response'], ci, cd)
display.print_json(result['response'])
else:
controlflow.system_error_exit(3, 'Update did not finish {result}')
'''gam info inboundssoassignment'''
def info_assignment():
ci = build()
cd = gapi_directory.build()
assignment = assignment_by_target(sys.argv[3], ci)
update_assignment_target_names(assignment, ci, cd)
profile = assignment.get('samlSsoInfo', {}).get('inboundSamlSsoProfile')
if profile:
assignment['samlSsoInfo']['inboundSamlSsoProfile'] = \
info_profile(return_only=True, displayName=f'id:{profile}', ci=ci)
display.print_json(assignment)
'''gam print inboundssoassignments'''
def print_show_assignments(action='print'):
ci = build()
cd = gapi_directory.build()
customer = get_sso_customer()
_filter = f'customer=="{customer}"'
todrive = False
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'todrive':
todrive = True
i += 1
else:
controlflow.invalid_argument_exit(myarg,
f'gam {action} inboundssoassignments')
assignments = gapi.get_all_pages(ci.inboundSsoAssignments(),
'list',
'inboundSsoAssignments',
filter=_filter)
if action == 'show':
for assignment in assignments:
update_assignment_target_names(assignment, ci, cd)
display.print_json(assignment)
print()
elif action == 'print':
titles = []
csv_rows = []
for assignment in assignments:
update_assignment_target_names(assignment, ci, cd)
csv_row = utils.flatten_json(assignment)
for item in csv_row:
if item not in titles:
titles.append(item)
csv_rows.append(csv_row)
display.write_csv_file(csv_rows,
titles,
'Inbound SSO Assignments',
todrive)

View File

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

View File

@@ -13,12 +13,14 @@ 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(),
results = gapi.call(crm.organizations(),
'search',
'organizations',
pageSize=1,
fields='organizations/name',
query=query)
if len(orgs) < 1:
orgs = results.get('organizations')
if not orgs:
# return nothing and let calling API deal with it
# since caller knows what GCP role would serve best
return
return orgs[0]['name']
return orgs[0].get('name')

View File

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

View File

@@ -21,36 +21,36 @@ def doGetCustomerInfo():
'get',
customerKey=customer_id)
print(f'Customer ID: {customer_info["id"]}')
print(f'Primary Domain: {customer_info["customerDomain"]}')
fields = 'domains(creationTime,domainName,isPrimary,verified)'
try:
result = gapi.call(
domains = gapi.call(
cd.domains(),
'get',
'list',
fields=fields,
customer=customer_id,
domainName=customer_info['customerDomain'],
fields='verified',
throw_reasons=[gapi.errors.ErrorReason.DOMAIN_NOT_FOUND])
throw_reasons=[gapi.errors.ErrorReason.DOMAIN_NOT_FOUND]).get('domains', [])
for domain in domains:
if domain.get('isPrimary'):
primary_domain = domain
break
else:
primary_domain = {}
except gapi.errors.GapiDomainNotFoundError:
result = {'verified': False}
print(f'Primary Domain Verified: {result["verified"]}')
# If customer has changed primary domain customerCreationTime is date
# of current primary being added, not customer create date.
# We should also get all domains and use oldest date
customer_creation = customer_info['customerCreationTime']
date_format = '%Y-%m-%dT%H:%M:%S.%fZ'
oldest = datetime.datetime.strptime(customer_creation, date_format)
domains = gapi.get_items(cd.domains(),
'list',
'domains',
customer=customer_id,
fields='domains(creationTime)')
primary_domain = {}
print(f'Primary Domain: {primary_domain.get("domainName", "Unknown")}')
print(f'Primary Domain Verified: {primary_domain.get("verified", "Unknown")}')
# we'll assume creation time is time of oldest domain customer has
oldest = 'Unknown'
for domain in domains:
creation_timestamp = int(domain['creationTime']) / 1000
domain_creation = datetime.datetime.fromtimestamp(creation_timestamp)
if domain_creation < oldest:
if oldest == 'Unknown' or domain_creation < oldest:
oldest = domain_creation
print(f'Customer Creation Time: {oldest.strftime(date_format)}')
customer_language = customer_info.get('language', 'Unset (defaults to en)')
if oldest != 'Unknown':
date_format = '%Y-%m-%dT%H:%M:%S.%fZ'
oldest = oldest.strftime(date_format)
print(f'Customer Creation Time: {oldest}')
customer_language = customer_info.get('language', 'Unset or Unknown (defaults to en)')
print(f'Default Language: {customer_language}')
if 'postalAddress' in customer_info:
print('Address:')
@@ -59,7 +59,7 @@ def doGetCustomerInfo():
print(f' {field}: {customer_info["postalAddress"][field]}')
if 'phoneNumber' in customer_info:
print(f'Phone: {customer_info["phoneNumber"]}')
print(f'Admin Secondary Email: {customer_info["alternateEmail"]}')
print(f'Admin Secondary Email: {customer_info.get("alternateEmail", "Unknown")}')
user_counts_map = {
'accounts:num_users': 'Total Users',
'accounts:gsuite_basic_total_licenses': 'G Suite Basic Licenses',
@@ -95,12 +95,10 @@ def doGetCustomerInfo():
continue
except gapi.errors.GapiForbiddenError:
return
warnings = result.get('warnings', [])
fullDataRequired = ['accounts']
usage = result.get('usageReports')
has_reports = bool(usage)
fullData, tryDate = gapi_reports._check_full_data_available(
warnings, tryDate, fullDataRequired, has_reports)
result, tryDate, fullDataRequired, False)
if fullData < 0:
print('No user report available.')
sys.exit(1)

View File

@@ -189,7 +189,7 @@ def info(group_name=None):
pass
print('')
print('Group Settings:')
for key, value in list(basic_info.items()):
for key, value in sorted(list(basic_info.items())):
if (key in ['kind', 'etag']) or ((key == 'aliases') and
(not getAliases)):
continue
@@ -199,7 +199,7 @@ def info(group_name=None):
print(f' {val}')
else:
print(f' {key}: {value}')
for key, value in list(settings.items()):
for key, value in sorted(list(settings.items())):
if key in ['kind', 'etag', 'description', 'email', 'name']:
continue
print(f' {key}: {value}')
@@ -1217,6 +1217,8 @@ GROUP_SETTINGS_LIST_PATTERN = re.compile(r'([A-Z][A-Z_]+[A-Z]?)')
def getGroupAttrValue(myarg, value, gs_object, gs_body, function):
if myarg == 'collaborative':
myarg = 'enablecollaborativeinbox'
elif myarg == 'gal':
myarg = 'includeinglobaladdresslist'
for (attrib,
params) in list(gs_object['schemas']['Groups']['properties'].items()):
if attrib in ['kind', 'etag', 'email']:

View File

@@ -373,17 +373,20 @@ def makeOrgUnitPathRelative(path):
def encodeOrgUnitPath(path):
if path.find('+') == -1 and path.find('%') == -1:
return path
encpath = ''
for c in path:
if c == '+':
encpath += '%2B'
elif c == '%':
encpath += '%25'
else:
encpath += c
return encpath
# 6.22 - This method no longer works.
# % no longer needs encoding and + is handled incorrectly in API with or without encoding
return path
# if path.find('+') == -1 and path.find('%') == -1:
# return path
# encpath = ''
# for c in path:
# if c == '+':
# encpath += '%2B'
# elif c == '%':
# encpath += '%25'
# else:
# encpath += c
# return encpath
def getOrgUnitItem(orgUnit, pathOnly=False, absolutePath=True):

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ import sys
from time import sleep
import gam
from gam import controlflow
from gam import display
from gam import gapi
from gam.gapi import directory as gapi_directory
@@ -18,7 +19,7 @@ def delete():
userKey=user_email,
throw_reasons=[gapi_errors.ErrorReason.CONDITION_NOT_MET])
except gam.gapi.errors.GapiConditionNotMetError as err:
display.print_error(
controlflow.system_error_exit(3,
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".'
)

View File

@@ -233,6 +233,7 @@ ERROR_REASON_TO_EXCEPTION = {
OAUTH2_TOKEN_ERRORS = [
'access_denied',
'access_denied: Requested client not authorized',
'access_denied: Account restricted',
'internal_failure: Backend Error',
'internal_failure: None',
'invalid_grant',

View File

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

View File

@@ -2,20 +2,150 @@ import base64
import os
import re
import sys
import time
import googleapiclient
from pathvalidate import sanitize_filepath
import gam
from gam.gapi import errors as gapi_errors
from gam.var import *
from gam import controlflow
from gam import fileutils
from gam import gapi
from gam import utils
def build_gapi():
def build():
return gam.buildGAPIObject('storage')
def copy_bucket():
s = build()
source_bucket = None
target_bucket = None
prefix = None
target_prefix = ''
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'sourcebucket':
source_bucket = sys.argv[i+1]
i += 2
elif myarg == 'targetbucket':
target_bucket = sys.argv[i+1]
i += 2
elif myarg == 'sourceprefix':
prefix = sys.argv[i+1]
i += 2
elif myarg == 'targetprefix':
target_prefix = sys.argv[i+1]
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam copy storagebucket')
if not target_bucket:
controlflow.missing_argument_exit('target_bucket', 'gam copy storagebucket')
if not source_bucket:
controlflow.missing_argument_exit('source_bucket', 'gam copy storagebucket')
page_message = gapi.got_total_items_msg('Storage Objects', '...\n')
objects = gapi.get_all_pages(s.objects(),
'list',
items='items',
page_message=page_message,
prefix=prefix,
bucket=source_bucket,
fields='items(name,bucket,md5Hash),nextPageToken')
copy_objects(objects,
target_bucket,
target_prefix)
def copy_objects(objects,
target_bucket,
target_prefix):
"""Copies objects to target_bucket.
Args:
objects: list of object dicts
[
{
bucket: source bucket,
name: source object name,
(optional) md5Hash: source file hash value
},
...
]
target_bucket: target bucket id
target_prefix: prefix name to prepend to target object
"""
def process_rewrite(request_id, response, exception):
file_ptr = int(request_id)
if exception:
# Poor man's backoff/retry
if exception.status_code == 429 or exception.status_code > 499:
print(f'Temporary error {exception.status_code}. Sleeping 10 seconds...')
time.sleep(10)
next_batch.add(s.objects().rewrite(**files_to_copy[file_ptr]['method']),
request_id=request_id)
return
else:
raise exception
file_count = file_ptr + 1
source_displayname = files_to_copy[file_ptr]['source_displayname']
target_displayname = files_to_copy[file_ptr]['target_displayname']
if response.get('done'):
source_md5 = files_to_copy[file_ptr]['md5Hash']
target_md5 = response['resource']['md5Hash']
if source_md5 != target_md5:
controlflow.system_error_exit(99, f'Target file {target_displayname} checksum {target_md5} does not match source {source_md5}. This should not happen')
else:
print(f'[ {file_count} / {total_files} ] 100% VERIFIED - finished copying:\n source: {source_displayname}\n dest: {target_displayname}')
else:
total_bytes = float(response.get('objectSize'))
done_bytes = float(response.get('totalBytesRewritten'))
pct = (done_bytes / total_bytes) * 100
print(f'[ {file_count} / {total_files} ] {pct:.2f}%\n source: {source_displayname}\n dest:{target_displayname}')
files_to_copy[file_ptr]['method']['rewriteToken'] = response.get('rewriteToken')
next_batch.add(s.objects().rewrite(**files_to_copy[file_ptr]['method']),
request_id=request_id)
s = build()
sbatch = s.new_batch_http_request(callback=process_rewrite)
files_to_copy = []
for object_ in objects:
files_to_copy.append(
{
'md5Hash': object_['md5Hash'],
'source_displayname': f'{object_["bucket"]}:{object_["name"]}',
'target_displayname': f'{target_bucket}:{target_prefix}{object_["name"]}',
'method': {
'destinationBucket': target_bucket,
'destinationObject': f'{target_prefix}{object_["name"]}',
'sourceBucket': object_['bucket'],
'sourceObject': object_['name'],
# 'maxBytesRewrittenPerCall': 1048576, # uncomment to easily test multiple rewrite API calls per object
},
})
i = 0
total_files = len(files_to_copy)
for file in files_to_copy:
while len(sbatch._order) == 100:
next_batch = s.new_batch_http_request(callback=process_rewrite)
sbatch.execute()
sbatch = next_batch
sbatch.add(s.objects().rewrite(**file['method']),
request_id=str(i))
i += 1
while len(sbatch._order) > 0:
next_batch = s.new_batch_http_request(callback=process_rewrite)
sbatch.execute()
sbatch = next_batch
print('All done!')
def get_cloud_storage_object(s,
bucket,
object_,
@@ -23,11 +153,12 @@ def get_cloud_storage_object(s,
expectedMd5=None):
if not local_file:
local_file = object_
local_file = sanitize_filepath(local_file, platform='auto')
if os.path.exists(local_file):
sys.stdout.write(' File already exists. ')
sys.stdout.write(f'File {local_file} already exists.')
sys.stdout.flush()
if expectedMd5:
sys.stdout.write(f'Verifying {expectedMd5} hash...')
sys.stdout.write(f' verifying {expectedMd5} hash...')
sys.stdout.flush()
if utils.md5_matches_file(local_file, expectedMd5, False):
print('VERIFIED')
@@ -35,7 +166,7 @@ def get_cloud_storage_object(s,
print('not verified. Downloading again and over-writing...')
else:
return # nothing to verify, just assume we're good.
print(f'saving to {local_file}')
print(f'Saving to {local_file}')
request = s.objects().get_media(bucket=bucket, object=object_)
file_path = os.path.dirname(local_file)
if not os.path.exists(file_path):
@@ -60,7 +191,7 @@ def get_cloud_storage_object(s,
def download_bucket():
bucket = sys.argv[3]
s = build_gapi()
s = build()
page_message = gapi.got_total_items_msg('Files', '...')
fields = 'nextPageToken,items(name,id,md5Hash)'
objects = gapi.get_all_pages(s.objects(),

View File

@@ -1,3 +1,4 @@
from base64 import b64encode
import datetime
import json
import sys
@@ -11,6 +12,7 @@ from gam import controlflow
from gam import display
from gam import fileutils
from gam import gapi
from gam.gapi import errors as gapi_errors
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
@@ -336,9 +338,9 @@ def print_count():
_validate_query(query, query_discovery)
body['query'] = query
operation = gapi.call(v.matters(), 'count', matterId=matterId, body=body)
print(f'Watching operation {operation["name"]}...')
sys.stderr.write(f'Watching operation {operation["name"]}...\n')
while not operation.get('done'):
print(f' operation {operation["name"]} is not done yet. Checking again in {operation_wait} seconds')
sys.stderr.write(f' operation {operation["name"]} is not done yet. Checking again in {operation_wait} seconds\n')
sleep(operation_wait)
operation = gapi.call(v.operations(), 'get', name=operation['name'])
response = operation.get('response', {})
@@ -518,7 +520,6 @@ def getHoldInfo():
def convertExportNameToID(v, nameOrID, matterId):
nameOrID = nameOrID.lower()
cg = UID_PATTERN.match(nameOrID)
if cg:
return cg.group(1)
@@ -529,7 +530,7 @@ def convertExportNameToID(v, nameOrID, matterId):
matterId=matterId,
fields=fields)
for export in exports:
if export['name'].lower() == nameOrID:
if export['name'].lower() == nameOrID.lower():
return export['id']
controlflow.system_error_exit(
4, f'could not find export name {nameOrID} '
@@ -682,18 +683,23 @@ def showHoldsForUsers(users):
v = buildGAPIObject()
matterIds = _getAllMatterIds(v)
matterHolds = {}
fields = 'holds(holdId,name,accounts(accountId,email),orgUnit),nextPageToken'
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)
try:
matterHolds[matterId] = gapi.get_all_pages(v.matters().holds(),
'list',
'holds',
fields=fields,
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O],
matterId=matterId)
except googleapiclient.errors.HttpError:
continue
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]:
for matterId, holds in matterHolds.items():
for hold in holds:
if 'orgUnit' in hold:
orgUnitId = hold['orgUnit'].get('orgUnitId')
if orgUnitId in orgUnits:
@@ -791,11 +797,49 @@ def getMatterInfo():
display.print_json(result)
def copyExport():
v = buildGAPIObject()
s = gapi_storage.build()
matterId = getMatterItem(v, sys.argv[3])
exportId = convertExportNameToID(v, sys.argv[4], matterId)
target_bucket = None
target_prefix = ''
i = 5
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'targetbucket':
target_bucket = sys.argv[i+1]
i += 2
elif myarg == 'targetprefix':
target_prefix = sys.argv[i+1]
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i],
'gam copy export')
if not target_bucket:
controlflow.missing_argument_exit('target_bucket', 'gam copy export')
export = gapi.call(v.matters().exports(),
'get',
matterId=matterId,
exportId=exportId)
objects = []
for s_file in export['cloudStorageSink']['files']:
# Convert to md5Hash format Storage API uses
# because OF COURSE they differ
md5Hash = b64encode(bytes.fromhex(s_file['md5Hash'])).decode()
objects.append({'bucket': s_file['bucketName'],
'name': s_file['objectName'],
'md5Hash': md5Hash})
gapi_storage.copy_objects(objects,
target_bucket,
target_prefix)
def downloadExport():
verifyFiles = True
extractFiles = True
v = buildGAPIObject()
s = gapi_storage.build_gapi()
s = gapi_storage.build()
matterId = getMatterItem(v, sys.argv[3])
exportId = convertExportNameToID(v, sys.argv[4], matterId)
targetFolder = GC_Values[GC_DRIVE_DIR]
@@ -823,24 +867,17 @@ def downloadExport():
for s_file in export['cloudStorageSink']['files']:
bucket = s_file['bucketName']
s_object = s_file['objectName']
filename = os.path.join(targetFolder, s_object.replace('/', '-'))
print(f'saving to {filename}')
request = s.objects().get_media(bucket=bucket, object=s_object)
f = fileutils.open_file(filename, 'wb')
downloader = googleapiclient.http.MediaIoBaseDownload(f, request)
done = False
while not done:
status, done = downloader.next_chunk()
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)
if verifyFiles:
expected_hash = s_file['md5Hash']
sys.stdout.write(f' Verifying file hash is {expected_hash}...')
sys.stdout.flush()
utils.md5_matches_file(filename, expected_hash, True)
print('VERIFIED')
else:
expected_hash = None
local_file = s_object.replace('/', '-').replace(':', '-')
filename = os.path.join(targetFolder, local_file)
gapi_storage.get_cloud_storage_object(s,
bucket,
s_object,
local_file=filename,
expectedMd5=expected_hash)
if extractFiles and re.search(r'\.zip$', filename):
gam.extract_nested_zip(filename, targetFolder)
@@ -976,10 +1013,14 @@ def printHolds():
for matterId in matterIds:
i += 1
sys.stderr.write(f'Retrieving holds for matter {matterId} ({i}/{matter_count})\n')
holds = gapi.get_all_pages(v.matters().holds(),
try:
holds = gapi.get_all_pages(v.matters().holds(),
'list',
'holds',
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O],
matterId=matterId)
except googleapiclient.errors.HttpError:
continue
for hold in holds:
display.add_row_titles_to_csv_file(
utils.flatten_json(hold, flattened={'matterId': matterId}),

View File

@@ -96,9 +96,13 @@ class _DeHTMLParser(HTMLParser):
re.sub(r'\n +', '\n', ''.join(self.__text))).strip()
def commonprefix(m):
def commonprefix(m, checkEnum=False):
'''Given a list of strings m, return string which is prefix common to all'''
s1 = min(m)
if checkEnum:
loc = s1.find('ENUM_')
if loc > 0:
return s1[:loc+5]
s2 = max(m)
for i, c in enumerate(s1):
if c != s2[i]:

View File

@@ -8,10 +8,10 @@ import platform
import re
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
GAM_VERSION = '6.20'
GAM_VERSION = '6.51'
GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
GAM_URL = 'https://git.io/gam'
GAM_URL = 'https://jaylee.us/gam'
GAM_INFO = (
f'GAM {GAM_VERSION} - {GAM_URL} / {GAM_AUTHOR} / '
f'Python {platform.python_version()} {sys.version_info.releaselevel} / '
@@ -31,7 +31,8 @@ usergroup_types = [
'ou_and_child', 'ou_and_children_ns', 'ou_and_child_ns',
'ou_and_children_susp', 'ou_and_child_susp', 'query', 'queries', 'license',
'licenses', 'licence', 'licences', 'file', 'csv', 'csvfile', 'all', 'cros',
'cros_sn', 'crosquery', 'crosqueries', 'crosfile', 'croscsv', 'croscsvfile'
'cros_sn', 'crosquery', 'crosqueries', 'crosfile', 'croscsv', 'croscsvfile',
'cros_ou', 'cros_ou_and_children'
]
ERROR_PREFIX = 'ERROR: '
WARNING_PREFIX = 'WARNING: '
@@ -116,11 +117,26 @@ 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'],
'displayName': 'Google Workspace for Education: Teaching and Learning Upgrade'
},
'1010390001': {
'product': '101039',
'aliases': ['assuredcontrols'],
'displayName': 'Assured Controls',
},
'1010400001': {
'product': '101040',
'aliases': ['beyondcorp', 'beyondcorpenterprise', 'bce'],
'displayName': 'Beyond Corp Enterprise',
},
'Google-Apps': {
'product': 'Google-Apps',
'aliases': ['standard', 'free'],
@@ -200,7 +216,7 @@ SKUS = {
},
'1010020030': {
'product': 'Google-Apps',
'aliases': ['workspacefrontline', 'workspacefrontlineworker'],
'aliases': ['wsflw', 'workspacefrontline', 'workspacefrontlineworker'],
'displayName': 'Workspace Frontline'
},
'1010340002': {
@@ -282,7 +298,10 @@ PRODUCTID_NAME_MAPPINGS = {
'101033': 'Google Voice',
'101034': 'G Suite Archived',
'101035': 'Cloud Search',
'101036': 'Google Meet Global Dialing',
'101037': 'G Suite Workspace for Education',
'101039': 'Assured Controls',
'101040': 'Beyond Corp',
'Google-Apps': 'Google Workspace',
'Google-Chrome-Device-Management': 'Google Chrome Device Management',
'Google-Drive-storage': 'Google Drive Storage',
@@ -641,6 +660,7 @@ MACOS_CODENAMES = {
},
11: 'Big Sur',
12: 'Monterey',
13: 'Ventura',
}
_MICROSOFT_FORMATS_LIST = [{
@@ -964,6 +984,8 @@ CROS_ARGUMENT_TO_PROPERTY_MAP = {
'autoupdateexpiration': ['autoUpdateExpiration',],
'bootmode': ['bootMode',],
'cpustatusreports': ['cpuStatusReports',],
'deprovisionreason': ['deprovisionReason',],
'lastDeprovisionTimestamp': ['lastDeprovisionTimestamp',],
'devicefiles': ['deviceFiles',],
'deviceid': ['deviceId',],
'dockmacaddress': ['dockMacAddress',],
@@ -971,6 +993,7 @@ CROS_ARGUMENT_TO_PROPERTY_MAP = {
'ethernetmacaddress': ['ethernetMacAddress',],
'ethernetmacaddress0': ['ethernetMacAddress0',],
'firmwareversion': ['firmwareVersion',],
'firstenrollmenttime': ['firstEnrollmentTime',],
'lastenrollmenttime': ['lastEnrollmentTime',],
'lastknownnetwork': ['lastKnownNetwork'],
'lastsync': ['lastSync',],
@@ -982,6 +1005,7 @@ CROS_ARGUMENT_TO_PROPERTY_MAP = {
'notes': ['notes',],
'ordernumber': ['orderNumber',],
'org': ['orgUnitPath',],
'orgunitid': ['orgUnitId',],
'orgunitpath': ['orgUnitPath',],
'osversion': ['osVersion',],
'ou': ['orgUnitPath',],
@@ -1007,6 +1031,7 @@ CROS_BASIC_FIELDS_LIST = [
]
CROS_SCALAR_PROPERTY_PRINT_ORDER = [
'orgUnitId',
'orgUnitPath',
'annotatedAssetId',
'annotatedLocation',
@@ -1026,7 +1051,10 @@ CROS_SCALAR_PROPERTY_PRINT_ORDER = [
'ethernetMacAddress0',
'macAddress',
'systemRamTotal',
'firstEnrollmentTime',
'lastEnrollmentTime',
'deprovisionReason',
'lastDeprovisionTimestamp',
'orderNumber',
'manufactureDate',
'supportEndDate',
@@ -1193,6 +1221,7 @@ _DEFAULT_CHARSET = UTF8
_FN_CLIENT_SECRETS_JSON = 'client_secrets.json'
_FN_OAUTH2SERVICE_JSON = 'oauth2service.json'
_FN_OAUTH2_TXT = 'oauth2.txt'
_FN_ROOTS_PEM = 'roots.pem'
#
GM_Globals = {
GM_SYSEXITRC: 0,
@@ -1260,6 +1289,9 @@ GC_ENABLE_DASA = 'enabledasa'
# and doRequestOAuth prints a link and waits for the verification code when
# oauth2.txt is being created
GC_NO_BROWSER = 'no_browser'
# If low memory is True, GAM tries to save RAM by writing pages to disk
# temporarily
GC_LOW_MEMORY = 'low_memory'
# If no_tdemail is True, writeCSVfile won't send an email
GC_NO_TDEMAIL = 'no_tdemail'
# oauth_browser forces usage of web server OAuth flow that proved problematic.
@@ -1315,6 +1347,7 @@ GC_Defaults = {
GC_DOMAIN: '',
GC_DRIVE_DIR: '',
GC_ENABLE_DASA: False,
GC_LOW_MEMORY: False,
GC_NO_BROWSER: False,
GC_NO_TDEMAIL: False,
GC_NO_CACHE: False,
@@ -1334,7 +1367,7 @@ GC_Defaults = {
GC_CSV_ROW_DROP_FILTER: '',
GC_TLS_MIN_VERSION: TLS_MIN,
GC_TLS_MAX_VERSION: None,
GC_CA_FILE: None,
GC_CA_FILE: _FN_ROOTS_PEM,
}
GC_Values = {}
@@ -1399,6 +1432,9 @@ GC_VAR_INFO = {
GC_ENABLE_DASA: {
GC_VAR_TYPE: GC_TYPE_BOOLEAN
},
GC_LOW_MEMORY: {
GC_VAR_TYPE: GC_TYPE_BOOLEAN
},
GC_NO_BROWSER: {
GC_VAR_TYPE: GC_TYPE_BOOLEAN
},

View File

@@ -10,4 +10,4 @@ importlib.metadata; python_version < '3.8'
passlib>=1.7.2
pathvalidate
python-dateutil
yubikey-manager>=4.0.0
yubikey-manager>=5.0

1130
src/roots.pem Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[metadata]
name = GAM for Google Workspace
version = 6.0.20
version = attr: gam.var.GAM_VERSION
description = Command line management for Google Workspaces
long_description = file: readme.md
long_description_content_type = text/markdown
@@ -37,6 +37,9 @@ install_requires =
yubikey-manager >= 4.0.0
pathvalidate
[options.package_data]
* = *.pem
# used during pip install .[test]
[options.extras_require]
test = pre-commit