Compare commits

...

30 Commits

Author SHA1 Message Date
Ross Scroggs
76aa1d1cdd print threads, print filelist countsonly update/fixes
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2025-02-11 21:05:34 -08:00
Jay Lee
7bdee0927a actions: run more steps on manual actions button push
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2025-02-11 10:25:57 -05:00
Jay Lee
b18d8a0107 actions: allow manual actions from GitHub UI
https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#workflow_dispatch
2025-02-11 09:17:17 -05:00
Jay Lee
5ed5540746 enable manual pypi runs
Some checks failed
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-apis (push) Has been cancelled
Build and test GAM / merge (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
2025-02-08 19:50:00 +00:00
Jay Lee
0a9ea7fc83 remove unknown classifier 2025-02-08 19:46:32 +00:00
Jay Lee
7ece7a0a1a 3.7.8, fix yubikey optional for pip package 2025-02-08 19:43:17 +00:00
Ross Scroggs
8d9f5689f3 Updated gam create vaultexport to include corpus gemini. 2025-02-08 09:38:37 -08:00
Ross Scroggs
8cd378ff8f Added option rawfields <BrowserFieldNameList> to gam info|print|show browsers
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2025-02-07 14:09:24 -08:00
Jay Lee
c0e037dda5 GAM 7.03.06 2025-02-07 21:11:01 +00:00
Jay Lee
b5730aadce gam print browsers rawfields. Starts #1746
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2025-02-07 14:49:46 +00:00
Jay Lee
4ec58bb844 actions: first attempt at auto publishing to PyPi
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2025-02-07 01:13:57 +00:00
Jay Lee
3ba99582dc GAM 7.03.05
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2025-02-06 22:24:30 +00:00
Jay Lee
b61a4f5115 [no ci] pypi: add gam a command 2025-02-06 17:22:07 -05:00
Jay Lee
7ce83b4623 fix YK as optional feature 2025-02-06 21:44:36 +00:00
Jay Lee
a58a998b49 make YK optional, 'pip install gam7[yubikey]' 2025-02-06 21:40:46 +00:00
Jay Lee
4e04bd7c51 Update pyproject.toml 2025-02-06 15:57:33 -05:00
Jay Lee
779ac0a6a0 Update pyproject.toml 2025-02-06 15:49:48 -05:00
Jay Lee
f18b7258bb Update pyproject.toml 2025-02-06 14:32:34 -05:00
Jay Lee
d4932c9d39 fix pyproject.toml license 2025-02-06 14:27:49 -05:00
Jay Lee
352845e482 pypi: initial package attempt for pip 2025-02-06 14:25:16 -05:00
Jay Lee
ff49c67580 Update build.yml
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2025-02-05 12:25:19 -05:00
Jay Lee
efee86cd33 actions: scratch build
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2025-02-05 08:41:46 -05:00
Ross Scroggs
a42eebdae1 Added option security to gam create cigroup
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2025-02-04 14:18:46 -08:00
Jay Lee
05333d9521 rebuild for Python 3.13.2
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2025-02-04 16:11:30 -05:00
Jay Lee
b04ba4b618 create cigroup now supports security label
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2025-02-03 15:55:59 +00:00
Ross Scroggs
c8108dace0 Fixed bug in gam update resoldcustomer #1743
Some checks failed
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
Check for Google Root CA Updates / check-apis (push) Waiting to run
CodeQL / Analyze (python) (push) Has been cancelled
2025-02-02 08:49:24 -08:00
Ross Scroggs
83a70d656e Updated gam <UserTypeEntity> show labels nested to properly display label nesting when labels have embedded / characters in their names.
Some checks are pending
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2025-02-01 12:38:15 -08:00
Ross Scroggs
3a38609fbb Updated gam <UserTypeEntity> show labels nested to properly display label nesting when labels have embedded / characters in their names. 2025-02-01 12:34:27 -08:00
Ross Scroggs
e744aa29e3 Fix gam-install.sh #1741
Some checks failed
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-apis (push) Has been cancelled
Build and test GAM / merge (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
2025-01-30 19:09:20 -08:00
Ross Scroggs
367c23a13c Updated gam create project to retry the following unexpected error:
Some checks failed
Build and test GAM / build (Win64, build, 10, VC-WIN64A, windows-2022) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 3, linux-aarch64, ubuntu-24.04-arm) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 4, linux-aarch64, ubuntu-22.04-arm) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 6, linux-aarch64, ubuntu-22.04-arm, yes) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 8, darwin64-arm64, macos-14) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 9, darwin64-arm64, macos-15) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-22.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 2, linux-x86_64, ubuntu-24.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 5, linux-x86_64, ubuntu-22.04, yes) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 7, darwin64-x86_64, macos-13) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 11, ubuntu-24.04, 3.10) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 12, ubuntu-24.04, 3.11) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 13, ubuntu-24.04, 3.12) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-apis (push) Has been cancelled
Build and test GAM / merge (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
ERROR: 400 - invalidArgument - Service account gam-project-a1b2c@gam-project-a1b2c.iam.gserviceaccount.com does not exist.
2025-01-27 08:16:37 -08:00
10 changed files with 420 additions and 135 deletions

View File

@@ -5,6 +5,7 @@ on:
pull_request: pull_request:
schedule: schedule:
- cron: '37 22 * * *' - cron: '37 22 * * *'
workflow_dispatch:
permissions: permissions:
contents: read contents: read
@@ -17,7 +18,7 @@ defaults:
working-directory: src working-directory: src
env: env:
SCRATCH_COUNTER: 7 SCRATCH_COUNTER: 9
OPENSSL_CONFIG_OPTS: no-fips --api=3.0.0 OPENSSL_CONFIG_OPTS: no-fips --api=3.0.0
OPENSSL_INSTALL_PATH: ${{ github.workspace }}/bin/ssl OPENSSL_INSTALL_PATH: ${{ github.workspace }}/bin/ssl
OPENSSL_SOURCE_PATH: ${{ github.workspace }}/src/openssl OPENSSL_SOURCE_PATH: ${{ github.workspace }}/src/openssl
@@ -129,7 +130,7 @@ jobs:
with: with:
path: | path: |
cache.tar.xz cache.tar.xz
key: gam-${{ matrix.jid }}-20250116 key: gam-${{ matrix.jid }}-20250204
- name: Untar Cache archive - name: Untar Cache archive
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true' if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
@@ -756,7 +757,7 @@ jobs:
- name: Attest that gam package files were generated from this Action - name: Attest that gam package files were generated from this Action
uses: actions/attest-build-provenance@v1 uses: actions/attest-build-provenance@v1
if: (github.event_name == 'push' || github.event_name == 'schedule') && matrix.goal == 'build' if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.goal == 'build'
with: with:
subject-path: | subject-path: |
gam*.tar.xz gam*.tar.xz
@@ -765,7 +766,7 @@ jobs:
- name: Archive production artifacts - name: Archive production artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
if: (github.event_name == 'push' || github.event_name == 'schedule') && matrix.goal != 'test' if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.goal != 'test'
with: with:
name: gam-binaries-${{ env.GAMOS }}-${{ env.arch }}-${{ matrix.jid }} name: gam-binaries-${{ env.GAMOS }}-${{ env.arch }}-${{ matrix.jid }}
path: | path: |
@@ -793,8 +794,8 @@ jobs:
fi fi
echo "We successfully compiled Python ${this_python} and OpenSSL ${this_openssl}" echo "We successfully compiled Python ${this_python} and OpenSSL ${this_openssl}"
- name: Live API tests push only - name: Live API tests
if: (github.event_name == 'push' || github.event_name == 'schedule') if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
run: | run: |
export gam_user="gam-gha-${JID}@pdl.jaylee.us" export gam_user="gam-gha-${JID}@pdl.jaylee.us"
echo "gam_user=${gam_user}" >> $GITHUB_ENV echo "gam_user=${gam_user}" >> $GITHUB_ENV
@@ -1004,7 +1005,7 @@ jobs:
tar cJvvf cache.tar.xz $tar_folders tar cJvvf cache.tar.xz $tar_folders
merge: merge:
if: (github.event_name == 'push' || github.event_name == 'schedule') if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: build needs: build
permissions: permissions:
@@ -1018,7 +1019,7 @@ jobs:
pattern: gam-binaries-* pattern: gam-binaries-*
publish: publish:
if: github.event_name == 'push' if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
needs: merge needs: merge
permissions: permissions:

33
.github/workflows/pypi.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: build and publish releases to PyPi
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
workflow_dispatch:
jobs:
pypi:
name: Upload release to PyPI
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/gam7
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
- name: Install required packages to publish
run: |
python3 -m pip install --upgrade build
- name: Build packages
run: |
python -m build
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

63
pyproject.toml Normal file
View File

@@ -0,0 +1,63 @@
[project]
name = "gam7"
dynamic = [
"version",
]
authors = [
{ name="Jay Lee", email="jay0lee@gmail.com" },
{ name="Ross Scroggs", email="Ross.Scroggs@gmail.com" },
]
dependencies = [
"chardet",
"cryptography",
"distro; sys_platform=='linux'",
"filelock",
"google-api-python-client>=2.1",
"google-auth-httplib2",
"google-auth-oauthlib>=0.4.1",
"google-auth>=2.3.2",
"httplib2>=0.17.0",
"lxml",
"passlib>=1.7.2",
"pathvalidate",
"python-dateutil",
]
description = "CLI tool to manage Google Workspace"
readme = "README.md"
requires-python = ">=3.9"
classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Operating System :: OS Independent",
]
license = {text = "Apache License (2.0)"}
license-files = ["LICEN[CS]E*"]
[project.optional-dependencies]
yubikey = ["yubikey-manager>=5.0"]
[project.scripts]
gam = "gam.__main__:main"
[project.urls]
Homepage = "https://github.com/GAM-team/GAM"
Issues = "https://github.com/GAM-team/GAM/issues"
Discussion = "https://groups.google.com/group/google-apps-manager"
Chat = "https://git.io/gam-chat"
[tool.hatch.version]
path = "src/gam/__init__.py"
[tool.hatch.build.targets.wheel]
packages = ["src/gam"]
[build-system]
requires = [
"hatchling",
]
build-backend = "hatchling.build"

View File

@@ -2090,19 +2090,25 @@ gam move browsers ou|org|orgunit <OrgUnitPath>
[batchsize <Integer>] [batchsize <Integer>]
gam info browser <DeviceID> gam info browser <DeviceID>
[basic|full|annotated] <BrowserFieldName>* [fields <BrowserFieldNameList>] (basic|full|annotated |
(<BrowserFieldName>* [fields <BrowserFieldNameList>]) |
(rawfields "<BrowserFieldNameList>"))
[formatjson] [formatjson]
gam show browsers gam show browsers
([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser>)|(queries <QueryBrowserList>))|(select <BrowserEntity>)) ([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser>)|(queries <QueryBrowserList>))|(select <BrowserEntity>))
[querytime<String> <Time>] [querytime<String> <Time>]
[orderby <BrowserOrderByFieldName> [ascending|descending]] [orderby <BrowserOrderByFieldName> [ascending|descending]]
[basic|full|allfields|annotated] <BrowserFieldName>* [fields <BrowserFieldNameList>] (basic|full|annotated |
(<BrowserFieldName>* [fields <BrowserFieldNameList>]) |
(rawfields "<BrowserFieldNameList>"))
[formatjson] [formatjson]
gam print browsers [todrive <ToDriveAttribute>*] gam print browsers [todrive <ToDriveAttribute>*]
([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser>)|(queries <QueryBrowserList>))|(select <BrowserEntity>)) ([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser>)|(queries <QueryBrowserList>))|(select <BrowserEntity>))
[querytime<String> <Time>] [querytime<String> <Time>]
[orderby <BrowserOrderByFieldName> [ascending|descending]] [orderby <BrowserOrderByFieldName> [ascending|descending]]
[basic|full|allfields|annotated] <BrowserFieldName>* [fields <BrowserFieldNameList>] (basic|full|annotated |
(<BrowserFieldName>* [fields <BrowserFieldNameList>]) |
(rawfields "<BrowserFieldNameList>"))
[sortheaders] [sortheaders]
[formatjson [quotechar <Character>]] [formatjson [quotechar <Character>]]
@@ -3927,8 +3933,11 @@ gam print group-members [todrive <ToDriveAttribute>*]
updatetime updatetime
<CIGroupFieldNameList> ::= "<CIGroupFieldName>(,<CIGroupFieldName>)*" <CIGroupFieldNameList> ::= "<CIGroupFieldName>(,<CIGroupFieldName>)*"
gam create cigroup <EmailAddress> [copyfrom <GroupItem>] <GroupAttribute>* gam create cigroup <EmailAddress>
[makeowner] [alias|aliases <CIGroupAliasList>] [dynamic <QueryDynamicGroup>] [copyfrom <GroupItem>] <GroupAttribute>*
[makeowner] [alias|aliases <CIGroupAliasList>]
[security|makesecuritygroup]
[dynamic <QueryDynamicGroup>]
gam update cigroup <GroupEntity> [copyfrom <GroupItem>] <GroupAttribute> gam update cigroup <GroupEntity> [copyfrom <GroupItem>] <GroupAttribute>
[security|makesecuritygroup| [security|makesecuritygroup|
dynamicsecurity|makedynamicsecuritygroup| dynamicsecurity|makedynamicsecuritygroup|
@@ -4515,7 +4524,7 @@ gam report users|user [todrive <ToDriveAttribute>*]
(country|countrycode <String>) (country|countrycode <String>)
gam create|add resoldcustomer <CustomerDomain> (customer_auth_token <String>) <ResoldCustomerAttribute>+ gam create|add resoldcustomer <CustomerDomain> (customer_auth_token <String>) <ResoldCustomerAttribute>+
gam update resoldcustomer <CustomerID> [customer_auth_token <String>] <ResoldCustomerAttribues>+ gam update resoldcustomer <CustomerID> <ResoldCustomerAttribues>+
gam info resoldcustomer <CustomerID> [formatjson] gam info resoldcustomer <CustomerID> [formatjson]
gam create|add resoldsubscription <CustomerID> (sku <SKUID>) gam create|add resoldsubscription <CustomerID> (sku <SKUID>)
@@ -5261,7 +5270,7 @@ gam print vaultcounts [todrive <ToDriveAttributes>*]
gam print vaultcounts [todrive <ToDriveAttributes>*] gam print vaultcounts [todrive <ToDriveAttributes>*]
matter <MatterItem> operation <String> [wait <Integer>] matter <MatterItem> operation <String> [wait <Integer>]
gam create vaultexport|export matter <MatterItem> [name <String>] corpus calendar|drive|mail|groups|hangouts_chat|voice gam create vaultexport|export matter <MatterItem> [name <String>] corpus calendar|drive|gemini|groups|hangouts_chat|mail|voice
(accounts <EmailAddressEntity>) | (orgunit|org|ou <OrgUnitPath>) | everyone (accounts <EmailAddressEntity>) | (orgunit|org|ou <OrgUnitPath>) | everyone
(shareddrives|teamdrives <SharedDriveIDList>) | (rooms <RoomList>) | (sitesurl <URLList>) (shareddrives|teamdrives <SharedDriveIDList>) | (rooms <RoomList>) | (sitesurl <URLList>)
[scope all_data|held_data|unprocessed_data] [scope all_data|held_data|unprocessed_data]
@@ -5269,10 +5278,12 @@ gam create vaultexport|export matter <MatterItem> [name <String>] corpus calenda
[locationquery <StringList>] [peoplequery <StringList>] [minuswords <StringList>] [locationquery <StringList>] [peoplequery <StringList>] [minuswords <StringList>]
[responsestatuses <AttendeeStatus>(,<AttendeeStatus>)*] [calendarversiondate <Date>|<Time>] [responsestatuses <AttendeeStatus>(,<AttendeeStatus>)*] [calendarversiondate <Date>|<Time>]
[includeshareddrives <Boolean>] [driveversiondate <Date>|<Time>] [includeaccessinfo <Boolean>] [includeshareddrives <Boolean>] [driveversiondate <Date>|<Time>] [includeaccessinfo <Boolean>]
[driveclientsideencryption any|encrypted|unencrypted]
[includerooms <Boolean>] [includerooms <Boolean>]
[excludedrafts <Boolean>] [format mbox|pst] [excludedrafts <Boolean>] [mailclientsideencryption any|encrypted|unencrypted]
[showconfidentialmodecontent <Boolean>] [usenewexport <Boolean>] [exportlinkeddrivefiles <Boolean>] [showconfidentialmodecontent <Boolean>] [usenewexport <Boolean>] [exportlinkeddrivefiles <Boolean>]
[covereddata calllogs|textmessages|voicemails] [covereddata calllogs|textmessages|voicemails]
[format ics|mbox|pst|xml]
[region any|europe|us] [showdetails|returnidonly] [region any|europe|us] [showdetails|returnidonly]
gam delete vaultexport|export <ExportItem> matter <MatterItem> gam delete vaultexport|export <ExportItem> matter <MatterItem>
gam delete vaultexport|export <MatterItem> <ExportItem> gam delete vaultexport|export <MatterItem> <ExportItem>
@@ -7595,6 +7606,7 @@ gam <UserTypeEntity> show messages|threads
[countsonly|positivecountsonly] [useronly] [countsonly|positivecountsonly] [useronly]
[headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String>] [dateheaderconverttimezone [<Boolean>]] [headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String>] [dateheaderconverttimezone [<Boolean>]]
[showlabels] [delimiter <Character>] [showbody] [showhtml] [showdate] [showsize] [showsnippet] [showlabels] [delimiter <Character>] [showbody] [showhtml] [showdate] [showsize] [showsnippet]
[maxmessagesperthread <Number>]
[[attachmentnamepattern <RegularExpression>] [[attachmentnamepattern <RegularExpression>]
[showattachments [noshowtextplain]] [showattachments [noshowtextplain]]
[saveattachments [targetfolder <FilePath>] [overwrite [<Boolean>]]] [saveattachments [targetfolder <FilePath>] [overwrite [<Boolean>]]]
@@ -7606,6 +7618,7 @@ gam <UserTypeEntity> print messages|threads [todrive <ToDriveAttribute>*]
[countsonly|positivecountsonly] [useronly] [countsonly|positivecountsonly] [useronly]
[headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String> [dateheaderconverttimezone [<Boolean>]]] [headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String> [dateheaderconverttimezone [<Boolean>]]]
[showlabels] [delimiter <Character>] [showbody] [showhtml] [showdate] [showsize] [showsnippet] [showlabels] [delimiter <Character>] [showbody] [showhtml] [showdate] [showsize] [showsnippet]
[maxmessagesperthread <Number>]
[convertcrnl] [delimiter <Character>] [convertcrnl] [delimiter <Character>]
[[attachmentnamepattern <RegularExpression>] [[attachmentnamepattern <RegularExpression>]
[showattachments [noshowtextplain]]] [showattachments [noshowtextplain]]]

View File

@@ -1,3 +1,75 @@
7.03.09
Added option `maxmessagesperthread <Number>` to `gam <UserTypeEntity> print|show threads`
that limits the number of messages displayed per thread. The default is 0, there is no limit.
For example, this can be used if you anly want to see the first message of each thread.
```
gam user user@domain.com print|show threads maxmessagesperthread 1
```
Fixed bug in `gam <UserTypeEntity> print filelist countsonly` where extraneous columns
were displayed.
Fixed bug in `gam <UserTypeEntity> print filelist countsonly showsize` where sizes were
all shown as 0 unless`sizefield size` was specified.
7.03.08
Improved pip install.
Yubikey as optional should now be working also. So:
pip install --upgrade gam7
skips Yubikey.
To install with yubikey support (assuming you have installed the necessary swig and libpcsclite-dev packages already) run:
pip install --upgrade gam7[yubikey]
7.03.07
Updated `gam create vaultexport` to include `corpus gemini`.
* See: https://workspaceupdates.googleblog.com/2025/02/google-vault-now-supports-gemini.html
7.03.06
Added option `rawfields "<BrowserFieldNameList>"` to `gam info|print|show browsers` that allows
specification of complex field lists with selected subfields.
* See: https://github.com/GAM-team/GAM/wiki/Chrome-Browser-Cloud-Management#raw-fields
7.03.05
Make GAM pip-installable: "pip install gam7"
7.03.04
Added option `security` to `gam create cigroup` that allows creation of a security group
in a single command.
Updated to Python 3.13.2.
7.03.03
Fixed bug in `gam update resoldcustomer` that caused the following error:
```
ERROR: Got an unexpected keyword argument customerAuthToken
```
7.03.02
Updated `gam <UserTypeEntity> show labels nested` to properly display label nesting
when labels have embedded `/` characters in their names.
7.03.01
Updated `gam create project` to retry the following unexpected error:
```
ERROR: 400 - invalidArgument - Service account gam-project-a1b2c@gam-project-a1b2c.iam.gserviceaccount.com does not exist.
```
7.03.00 7.03.00
Updated `gam create|use project` to discontinue use of the `Identity-Aware Proxy (IAP) OAuth Admin APIs` Updated `gam create|use project` to discontinue use of the `Identity-Aware Proxy (IAP) OAuth Admin APIs`

View File

@@ -112,6 +112,12 @@ else
check_type="authenticated" check_type="authenticated"
curl_opts=( "$GHCLIENT" ) curl_opts=( "$GHCLIENT" )
fi fi
curl_ver=$(curl --version|head -1|cut -d " " -f 2)
if [[ "${curl_ver:0:4}" < "7.76" ]]; then
curl_fail=( )
else
curl_fail=( "--fail-with-body" )
fi
echo_yellow "Checking GitHub URL $release_url for $gamversion GAM release ($check_type)..." echo_yellow "Checking GitHub URL $release_url for $gamversion GAM release ($check_type)..."
release_json=$(curl \ release_json=$(curl \
--silent \ --silent \
@@ -119,7 +125,7 @@ release_json=$(curl \
-H "Accept: application/vnd.github+json" \ -H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \ -H "X-GitHub-Api-Version: 2022-11-28" \
"$release_url" \ "$release_url" \
--fail-with-body) "${curl_fail[@]}")
curl_exit_code=$? curl_exit_code=$?
if [ $curl_exit_code -ne 0 ]; then if [ $curl_exit_code -ne 0 ]; then
echo_red "ERROR retrieving URL: ${release_json}" echo_red "ERROR retrieving URL: ${release_json}"

View File

@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
""" """
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>' __author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
__version__ = '7.03.00' __version__ = '7.03.09'
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
#pylint: disable=wrong-import-position #pylint: disable=wrong-import-position
@@ -7356,6 +7356,12 @@ def addFieldToFieldsList(fieldName, fieldsChoiceMap, fieldsList):
def _getFieldsList(): def _getFieldsList():
return getString(Cmd.OB_FIELD_NAME_LIST).lower().replace('_', '').replace(',', ' ').split() return getString(Cmd.OB_FIELD_NAME_LIST).lower().replace('_', '').replace(',', ' ').split()
def _getRawFields(requiredField=None):
rawFields = getString(Cmd.OB_FIELDS)
if requiredField is None or requiredField in rawFields:
return rawFields
return f'{requiredField},{rawFields}'
def _addInitialField(fieldsList, initialField): def _addInitialField(fieldsList, initialField):
if isinstance(initialField, list): if isinstance(initialField, list):
fieldsList.extend(initialField) fieldsList.extend(initialField)
@@ -8489,14 +8495,18 @@ class CSVPrintFile():
self.AddTitle(title) self.AddTitle(title)
return RowFilterMatch(row, self.titlesList, self.rowFilter, self.rowFilterMode, self.rowDropFilter, self.rowDropFilterMode) return RowFilterMatch(row, self.titlesList, self.rowFilter, self.rowFilterMode, self.rowDropFilter, self.rowDropFilterMode)
def UpdateMimeTypeCounts(self, row, mimeTypeInfo): def UpdateMimeTypeCounts(self, row, mimeTypeInfo, sizeField):
saveList = self.titlesList[:]
saveSet = set(self.titlesSet)
for title in row: for title in row:
if title not in self.titlesSet: if title not in self.titlesSet:
self.AddTitle(title) self.AddTitle(title)
if RowFilterMatch(row, self.titlesList, self.rowFilter, self.rowFilterMode, self.rowDropFilter, self.rowDropFilterMode): if RowFilterMatch(row, self.titlesList, self.rowFilter, self.rowFilterMode, self.rowDropFilter, self.rowDropFilterMode):
mimeTypeInfo.setdefault(row['mimeType'], {'count': 0, 'size': 0}) mimeTypeInfo.setdefault(row['mimeType'], {'count': 0, 'size': 0})
mimeTypeInfo[row['mimeType']]['count'] += 1 mimeTypeInfo[row['mimeType']]['count'] += 1
mimeTypeInfo[row['mimeType']]['size'] += int(row.get('size', '0')) mimeTypeInfo[row['mimeType']]['size'] += int(row.get(sizeField, '0'))
self.titlesList = saveList[:]
self.titlesSet = set(saveSet)
def SetZeroBlankMimeTypeCounts(self, zeroBlankMimeTypeCounts): def SetZeroBlankMimeTypeCounts(self, zeroBlankMimeTypeCounts):
self.zeroBlankMimeTypeCounts = zeroBlankMimeTypeCounts self.zeroBlankMimeTypeCounts = zeroBlankMimeTypeCounts
@@ -11361,13 +11371,32 @@ def doEnableAPIs():
url = f'https://console.cloud.google.com/apis/enableflow?apiid={apiid}&project={projectId}' url = f'https://console.cloud.google.com/apis/enableflow?apiid={apiid}&project={projectId}'
writeStdout(f' {url}\n\n') writeStdout(f' {url}\n\n')
def _waitForSvcAcctCompletion(i):
sleep_time = i*5
if i > 3:
sys.stdout.write(Msg.WAITING_FOR_ITEM_CREATION_TO_COMPLETE_SLEEPING.format(Ent.Singular(Ent.SVCACCT), sleep_time))
time.sleep(sleep_time)
def _grantRotateRights(iam, projectId, service_account, email, account_type='serviceAccount'): def _grantRotateRights(iam, projectId, service_account, email, account_type='serviceAccount'):
printEntityMessage([Ent.PROJECT, projectId, Ent.SVCACCT, email],
Msg.HAS_RIGHTS_TO_ROTATE_OWN_PRIVATE_KEY.format(email, service_account))
body = {'policy': {'bindings': [{'role': 'roles/iam.serviceAccountKeyAdmin', body = {'policy': {'bindings': [{'role': 'roles/iam.serviceAccountKeyAdmin',
'members': [f'{account_type}:{email}']}]}} 'members': [f'{account_type}:{email}']}]}}
maxRetries = 10
printEntityMessage([Ent.PROJECT, projectId, Ent.SVCACCT, email],
Msg.HAS_RIGHTS_TO_ROTATE_OWN_PRIVATE_KEY.format(email, service_account))
for retry in range(1, maxRetries+1):
try:
callGAPI(iam.projects().serviceAccounts(), 'setIamPolicy', callGAPI(iam.projects().serviceAccounts(), 'setIamPolicy',
throwReasons=[GAPI.INVALID_ARGUMENT],
resource=f'projects/{projectId}/serviceAccounts/{service_account}', body=body) resource=f'projects/{projectId}/serviceAccounts/{service_account}', body=body)
return True
except GAPI.invalidArgument as e:
entityActionFailedWarning([Ent.PROJECT, projectId, Ent.SVCACCT, service_account], str(e))
if 'does not exist' not in str(e) or retry == maxRetries:
return False
_waitForSvcAcctCompletion(retry)
except Exception as e:
entityActionFailedWarning([Ent.PROJECT, projectId, Ent.SVCACCT, service_account], str(e))
return False
def _createOauth2serviceJSON(httpObj, projectInfo, svcAcctInfo, create_key=True): def _createOauth2serviceJSON(httpObj, projectInfo, svcAcctInfo, create_key=True):
iam = getAPIService(API.IAM, httpObj) iam = getAPIService(API.IAM, httpObj)
@@ -11392,8 +11421,7 @@ def _createOauth2serviceJSON(httpObj, projectInfo, svcAcctInfo, create_key=True)
clientId=service_account['uniqueId']): clientId=service_account['uniqueId']):
return False return False
sa_email = service_account['name'].rsplit('/', 1)[-1] sa_email = service_account['name'].rsplit('/', 1)[-1]
_grantRotateRights(iam, projectInfo['projectId'], sa_email, sa_email) return _grantRotateRights(iam, projectInfo['projectId'], sa_email, sa_email)
return True
def _createClientSecretsOauth2service(httpObj, login_hint, appInfo, projectInfo, svcAcctInfo, create_key=True): def _createClientSecretsOauth2service(httpObj, login_hint, appInfo, projectInfo, svcAcctInfo, create_key=True):
def _checkClientAndSecret(csHttpObj, client_id, client_secret): def _checkClientAndSecret(csHttpObj, client_id, client_secret):
@@ -12563,12 +12591,6 @@ def doProcessSvcAcctKeys(mode=None, iam=None, projectId=None, clientEmail=None,
else: else:
unknownArgumentExit() unknownArgumentExit()
def waitForCompletion(i):
sleep_time = i*5
if i > 3:
sys.stdout.write(Msg.WAITING_FOR_ITEM_CREATION_TO_COMPLETE_SLEEPING.format(Ent.Singular(Ent.SVCACCT), sleep_time))
time.sleep(sleep_time)
local_key_size = 2048 local_key_size = 2048
validityHours = 0 validityHours = 0
body = {} body = {}
@@ -12638,12 +12660,12 @@ def doProcessSvcAcctKeys(mode=None, iam=None, projectId=None, clientEmail=None,
if retry == maxRetries: if retry == maxRetries:
entityActionFailedWarning([Ent.PROJECT, projectId, Ent.SVCACCT, clientEmail], str(e)) entityActionFailedWarning([Ent.PROJECT, projectId, Ent.SVCACCT, clientEmail], str(e))
return False return False
waitForCompletion(retry) _waitForSvcAcctCompletion(retry)
except GAPI.permissionDenied: except GAPI.permissionDenied:
if retry == maxRetries: if retry == maxRetries:
entityActionFailedWarning([Ent.PROJECT, projectId, Ent.SVCACCT, clientEmail], Msg.UPDATE_PROJECT_TO_VIEW_MANAGE_SAKEYS) entityActionFailedWarning([Ent.PROJECT, projectId, Ent.SVCACCT, clientEmail], Msg.UPDATE_PROJECT_TO_VIEW_MANAGE_SAKEYS)
return False return False
waitForCompletion(retry) _waitForSvcAcctCompletion(retry)
except GAPI.badRequest as e: except GAPI.badRequest as e:
entityActionFailedWarning([Ent.PROJECT, projectId, Ent.SVCACCT, clientEmail], str(e)) entityActionFailedWarning([Ent.PROJECT, projectId, Ent.SVCACCT, clientEmail], str(e))
return False return False
@@ -12656,7 +12678,7 @@ def doProcessSvcAcctKeys(mode=None, iam=None, projectId=None, clientEmail=None,
new_data['private_key'] = '' new_data['private_key'] = ''
newPrivateKeyId = '' newPrivateKeyId = ''
break break
waitForCompletion(retry) _waitForSvcAcctCompletion(retry)
new_data['private_key_id'] = newPrivateKeyId new_data['private_key_id'] = newPrivateKeyId
oauth2service_data = _formatOAuth2ServiceData(new_data) oauth2service_data = _formatOAuth2ServiceData(new_data)
else: else:
@@ -12673,7 +12695,7 @@ def doProcessSvcAcctKeys(mode=None, iam=None, projectId=None, clientEmail=None,
if retry == maxRetries: if retry == maxRetries:
entityActionFailedWarning([Ent.PROJECT, projectId, Ent.SVCACCT, clientEmail], Msg.UPDATE_PROJECT_TO_VIEW_MANAGE_SAKEYS) entityActionFailedWarning([Ent.PROJECT, projectId, Ent.SVCACCT, clientEmail], Msg.UPDATE_PROJECT_TO_VIEW_MANAGE_SAKEYS)
return False return False
waitForCompletion(retry) _waitForSvcAcctCompletion(retry)
except GAPI.badRequest as e: except GAPI.badRequest as e:
entityActionFailedWarning([Ent.PROJECT, projectId, Ent.SVCACCT, clientEmail], str(e)) entityActionFailedWarning([Ent.PROJECT, projectId, Ent.SVCACCT, clientEmail], str(e))
return False return False
@@ -12714,7 +12736,7 @@ def doProcessSvcAcctKeys(mode=None, iam=None, projectId=None, clientEmail=None,
if retry == maxRetries: if retry == maxRetries:
entityActionFailedWarning([Ent.SVCACCT_KEY, keyName], Msg.UPDATE_PROJECT_TO_VIEW_MANAGE_SAKEYS) entityActionFailedWarning([Ent.SVCACCT_KEY, keyName], Msg.UPDATE_PROJECT_TO_VIEW_MANAGE_SAKEYS)
break break
waitForCompletion(retry) _waitForSvcAcctCompletion(retry)
except GAPI.badRequest as e: except GAPI.badRequest as e:
entityActionFailedWarning([Ent.SVCACCT_KEY, keyName], str(e), i, count) entityActionFailedWarning([Ent.SVCACCT_KEY, keyName], str(e), i, count)
break break
@@ -15094,15 +15116,15 @@ def doCreateResoldCustomer():
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden, GAPI.invalid) as e: except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden, GAPI.invalid) as e:
entityActionFailedWarning([Ent.CUSTOMER_DOMAIN, body['customerDomain']], str(e)) entityActionFailedWarning([Ent.CUSTOMER_DOMAIN, body['customerDomain']], str(e))
# gam update resoldcustomer <CustomerID> [customer_auth_token <String>] <ResoldCustomerAttribute>+ # gam update resoldcustomer <CustomerID> <ResoldCustomerAttribute>+
def doUpdateResoldCustomer(): def doUpdateResoldCustomer():
res = buildGAPIObject(API.RESELLER) res = buildGAPIObject(API.RESELLER)
customerId = getString(Cmd.OB_CUSTOMER_ID) customerId = getString(Cmd.OB_CUSTOMER_ID)
customerAuthToken, body = _getResoldCustomerAttr() _, body = _getResoldCustomerAttr()
try: try:
callGAPI(res.customers(), 'patch', callGAPI(res.customers(), 'patch',
throwReasons=GAPI.RESELLER_THROW_REASONS, throwReasons=GAPI.RESELLER_THROW_REASONS,
customerId=customerId, body=body, customerAuthToken=customerAuthToken, fields='') customerId=customerId, body=body, fields='')
entityActionPerformed([Ent.CUSTOMER_ID, customerId]) entityActionPerformed([Ent.CUSTOMER_ID, customerId])
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden, GAPI.invalid) as e: except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden, GAPI.invalid) as e:
entityActionFailedWarning([Ent.CUSTOMER_ID, customerId], str(e)) entityActionFailedWarning([Ent.CUSTOMER_ID, customerId], str(e))
@@ -15121,6 +15143,7 @@ def doInfoResoldCustomer():
customerId=customerId) customerId=customerId)
if not FJQC.formatJSON: if not FJQC.formatJSON:
printKeyValueList(['Customer ID', customerInfo['customerId']]) printKeyValueList(['Customer ID', customerInfo['customerId']])
printKeyValueList(['Customer Type', customerInfo['customerType']])
printKeyValueList(['Customer Domain', customerInfo['customerDomain']]) printKeyValueList(['Customer Domain', customerInfo['customerDomain']])
if 'customerDomainVerified' in customerInfo: if 'customerDomainVerified' in customerInfo:
printKeyValueList(['Customer Domain Verified', customerInfo['customerDomainVerified']]) printKeyValueList(['Customer Domain Verified', customerInfo['customerDomainVerified']])
@@ -16555,7 +16578,7 @@ def doInfoAdminRole():
fieldsList.append('rolePrivileges') fieldsList.append('rolePrivileges')
else: else:
unknownArgumentExit() unknownArgumentExit()
fields = ','.join(set(fieldsList)) fields = getFieldsFromFieldsList(fieldsList)
try: try:
role = callGAPI(cd.roles(), 'get', role = callGAPI(cd.roles(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.FAILED_PRECONDITION, throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.FAILED_PRECONDITION,
@@ -17692,9 +17715,9 @@ def _getOrgUnits(cd, orgUnitPath, fieldsList, listType, showParent, batchSubOrgs
if 'parentOrgUnitId' not in fieldsList: if 'parentOrgUnitId' not in fieldsList:
localFieldsList.append('parentOrgUnitId') localFieldsList.append('parentOrgUnitId')
deleteParentOrgUnitId = True deleteParentOrgUnitId = True
fields = ','.join(set(localFieldsList)) fields = getFieldsFromFieldsList(localFieldsList)
else: else:
fields = ','.join(set(fieldsList)) fields = getFieldsFromFieldsList(fieldsList)
listfields = f'organizationUnits({fields})' listfields = f'organizationUnits({fields})'
if listType == 'all' and orgUnitPath == '/': if listType == 'all' and orgUnitPath == '/':
printGettingAllAccountEntities(Ent.ORGANIZATIONAL_UNIT) printGettingAllAccountEntities(Ent.ORGANIZATIONAL_UNIT)
@@ -22156,8 +22179,7 @@ def printShowUserPeopleContacts(users):
if not fieldsList: if not fieldsList:
ofields = _getPersonFields(PEOPLE_OTHER_CONTACTS_FIELDS_CHOICE_MAP, PEOPLE_CONTACTS_DEFAULT_FIELDS, fieldsList, parameters) ofields = _getPersonFields(PEOPLE_OTHER_CONTACTS_FIELDS_CHOICE_MAP, PEOPLE_CONTACTS_DEFAULT_FIELDS, fieldsList, parameters)
else: else:
fieldsList = [PEOPLE_OTHER_CONTACTS_FIELDS_CHOICE_MAP[field.lower()] for field in fieldsList if field.lower() in PEOPLE_OTHER_CONTACTS_FIELDS_CHOICE_MAP] ofields = getFieldsFromFieldsList([PEOPLE_OTHER_CONTACTS_FIELDS_CHOICE_MAP[field.lower()] for field in fieldsList if field.lower() in PEOPLE_OTHER_CONTACTS_FIELDS_CHOICE_MAP])
ofields = ','.join(set(fieldsList))
i, count, users = getEntityArgument(users) i, count, users = getEntityArgument(users)
for user in users: for user in users:
i += 1 i += 1
@@ -24771,7 +24793,7 @@ def doPrintCrOSActivity(entityList=None):
else: else:
sortRows = True sortRows = True
jcount = len(entityList) jcount = len(entityList)
fields = ','.join(set(fieldsList)) fields = getFieldsFromFieldsList(fieldsList)
svcargs = dict([('customerId', GC.Values[GC.CUSTOMER_ID]), ('deviceId', None), ('projection', projection), ('fields', fields)]+GM.Globals[GM.EXTRA_ARGS_LIST]) svcargs = dict([('customerId', GC.Values[GC.CUSTOMER_ID]), ('deviceId', None), ('projection', projection), ('fields', fields)]+GM.Globals[GM.EXTRA_ARGS_LIST])
method = getattr(cd.chromeosdevices(), 'get') method = getattr(cd.chromeosdevices(), 'get')
dbatch = cd.new_batch_http_request(callback=_callbackPrintCrOS) dbatch = cd.new_batch_http_request(callback=_callbackPrintCrOS)
@@ -25061,7 +25083,7 @@ def _showBrowser(browser, FJQC, i=0, count=0):
return return
printEntity([Ent.CHROME_BROWSER, browser['deviceId']], i, count) printEntity([Ent.CHROME_BROWSER, browser['deviceId']], i, count)
Ind.Increment() Ind.Increment()
showJSON(None, browser, timeObjects=BROWSER_TIME_OBJECTS) showJSON(None, browser, timeObjects=BROWSER_TIME_OBJECTS, dictObjectsKey={'machinePolicies': 'name'})
Ind.Decrement() Ind.Decrement()
BROWSER_FIELDS_CHOICE_MAP = { BROWSER_FIELDS_CHOICE_MAP = {
@@ -25101,11 +25123,13 @@ BROWSER_FIELDS_CHOICE_MAP = {
'user': 'annotatedUser', 'user': 'annotatedUser',
'virtualdeviceid': 'virtualDeviceId', 'virtualdeviceid': 'virtualDeviceId',
} }
BROWSER_ANNOTATED_FIELDS_LIST = ['annotatedAssetId', 'annotatedLocation', 'annotatedNotes', 'annotatedUser'] BROWSER_ANNOTATED_FIELDS_LIST = ['annotatedAssetId', 'annotatedLocation', 'annotatedNotes', 'annotatedUser', 'deviceId']
BROWSER_FULL_ACCESS_FIELDS = {'browsers', 'lastDeviceUsers', 'lastStatusReportTime', 'machinePolicies'} BROWSER_FULL_ACCESS_FIELDS = {'browsers', 'lastDeviceUsers', 'lastStatusReportTime', 'machinePolicies'}
# gam info browser <DeviceID> # gam info browser <DeviceID>
# [basic|full|annotated] <BrowserFieldName>* [fields <BrowserFieldNameList>] # (basic|full|annotated |
# (<BrowserFieldName>* [fields <BrowserFieldNameList>]) |
# (rawfields <BrowserFieldNameList>))
# [formatjson] # [formatjson]
def doInfoBrowsers(): def doInfoBrowsers():
cbcm = buildGAPIObject(API.CBCM) cbcm = buildGAPIObject(API.CBCM)
@@ -25113,6 +25137,7 @@ def doInfoBrowsers():
deviceId = getString(Cmd.OB_DEVICE_ID) deviceId = getString(Cmd.OB_DEVICE_ID)
projection = 'BASIC' projection = 'BASIC'
fieldsList = [] fieldsList = []
rawFields = None
FJQC = FormatJSONQuoteChar() FJQC = FormatJSONQuoteChar()
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
myarg = getArgument() myarg = getArgument()
@@ -25124,16 +25149,21 @@ def doInfoBrowsers():
fieldsList = [] fieldsList = []
elif getFieldsList(myarg, BROWSER_FIELDS_CHOICE_MAP, fieldsList, initialField='deviceId'): elif getFieldsList(myarg, BROWSER_FIELDS_CHOICE_MAP, fieldsList, initialField='deviceId'):
pass pass
elif myarg == 'rawfields':
projection = 'FULL'
rawFields = _getRawFields('deviceId')
else: else:
FJQC.GetFormatJSON(myarg) FJQC.GetFormatJSON(myarg)
if projection == 'BASIC' and set(fieldsList).intersection(BROWSER_FULL_ACCESS_FIELDS): if projection == 'BASIC' and set(fieldsList).intersection(BROWSER_FULL_ACCESS_FIELDS):
projection = 'FULL' projection = 'FULL'
fields = ','.join(set(fieldsList)) fields = getFieldsFromFieldsList(fieldsList) if not rawFields else rawFields
try: try:
browser = callGAPI(cbcm.chromebrowsers(), 'get', browser = callGAPI(cbcm.chromebrowsers(), 'get',
throwReasons=[GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN], throwReasons=[GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.FORBIDDEN],
customer=customerId, deviceId=deviceId, projection=projection, fields=fields) customer=customerId, deviceId=deviceId, projection=projection, fields=fields)
_showBrowser(browser, FJQC) _showBrowser(browser, FJQC)
except GAPI.invalidArgument as e:
entityActionFailedWarning([Ent.CHROME_BROWSER, deviceId], str(e))
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden): except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden):
checkEntityAFDNEorAccessErrorExit(None, Ent.CHROME_BROWSER, deviceId) checkEntityAFDNEorAccessErrorExit(None, Ent.CHROME_BROWSER, deviceId)
@@ -25334,7 +25364,7 @@ def doInfoChromeProfile():
pass pass
else: else:
FJQC.GetFormatJSON(myarg) FJQC.GetFormatJSON(myarg)
fields = ','.join(set(fieldsList)) fields = getFieldsFromFieldsList(fieldsList)
try: try:
profile = callGAPI(cm.customers().profiles(), 'get', profile = callGAPI(cm.customers().profiles(), 'get',
throwReasons=[GAPI.INVALID_ARGUMENT, GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED], throwReasons=[GAPI.INVALID_ARGUMENT, GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED],
@@ -25466,13 +25496,19 @@ BROWSER_ORDERBY_CHOICE_MAP = {
# ([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser)|(queries <QueryBrowserList>))|(select <BrowserEntity>)) # ([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser)|(queries <QueryBrowserList>))|(select <BrowserEntity>))
# [querytime<String> <Time>] # [querytime<String> <Time>]
# [orderby <BrowserOrderByFieldName> [ascending|descending]] # [orderby <BrowserOrderByFieldName> [ascending|descending]]
# [basic|full|allfields|annotated] <BrowserFieldName>* [fields <BrowserFieldNameList>] # (basic|full|annotated |
# (<BrowserFieldName>* [fields <BrowserFieldNameList>]) |
# (rawfields <BrowserFieldNameList>))
# (<BrowserFieldName>* [fields <BrowserFieldNameList>]|(rawfields <BrowserFieldNameList>)
# [formatjson] # [formatjson]
# gam print browsers [todrive <ToDriveAttribute>*] # gam print browsers [todrive <ToDriveAttribute>*]
# ([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser)|(queries <QueryBrowserList>))|(select <BrowserEntity>)) # ([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser)|(queries <QueryBrowserList>))|(select <BrowserEntity>))
# [querytime<String> <Time>] # [querytime<String> <Time>]
# [orderby <BrowserOrderByFieldName> [ascending|descending]] # [orderby <BrowserOrderByFieldName> [ascending|descending]]
# [basic|full|allfields|annotated] <BrowserFieldName>* [fields <BrowserFieldNameList>] # (basic|full|annotated |
# (<BrowserFieldName>* [fields <BrowserFieldNameList>]) |
# (rawfields <BrowserFieldNameList>))
# (<BrowserFieldName>* [fields <BrowserFieldNameList>]|(rawfields <BrowserFieldNameList>)
# [sortheaders] [formatjson [quotechar <Character>]] # [sortheaders] [formatjson [quotechar <Character>]]
def doPrintShowBrowsers(): def doPrintShowBrowsers():
def _printBrowser(browser): def _printBrowser(browser):
@@ -25489,6 +25525,7 @@ def doPrintShowBrowsers():
csvPF = CSVPrintFile(['deviceId']) if Act.csvFormat() else None csvPF = CSVPrintFile(['deviceId']) if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF) FJQC = FormatJSONQuoteChar(csvPF)
fieldsList = [] fieldsList = []
rawFields = None
projection = 'BASIC' projection = 'BASIC'
orderBy = 'id' orderBy = 'id'
sortOrder = 'ASCENDING' sortOrder = 'ASCENDING'
@@ -25527,22 +25564,25 @@ def doPrintShowBrowsers():
sortHeaders = True sortHeaders = True
elif getFieldsList(myarg, BROWSER_FIELDS_CHOICE_MAP, fieldsList, initialField='deviceId'): elif getFieldsList(myarg, BROWSER_FIELDS_CHOICE_MAP, fieldsList, initialField='deviceId'):
pass pass
elif myarg == 'rawfields':
projection = 'FULL'
rawFields = _getRawFields('deviceId')
else: else:
FJQC.GetFormatJSONQuoteChar(myarg, True) FJQC.GetFormatJSONQuoteChar(myarg, True)
if projection == 'BASIC' and set(fieldsList).intersection(BROWSER_FULL_ACCESS_FIELDS): if projection == 'BASIC' and set(fieldsList).intersection(BROWSER_FULL_ACCESS_FIELDS):
projection = 'FULL' projection = 'FULL'
fields = getItemFieldsFromFieldsList('browsers', fieldsList)
if FJQC.formatJSON: if FJQC.formatJSON:
sortHeaders = False sortHeaders = False
substituteQueryTimes(queries, queryTimes) substituteQueryTimes(queries, queryTimes)
if entityList is None: if entityList is None:
fields = getItemFieldsFromFieldsList('browsers', fieldsList) if not rawFields else f'nextPageToken,browsers({rawFields})'
for query in queries: for query in queries:
printGettingAllAccountEntities(Ent.CHROME_BROWSER, query) printGettingAllAccountEntities(Ent.CHROME_BROWSER, query)
pageMessage = getPageMessage() pageMessage = getPageMessage()
try: try:
feed = yieldGAPIpages(cbcm.chromebrowsers(), 'list', 'browsers', feed = yieldGAPIpages(cbcm.chromebrowsers(), 'list', 'browsers',
pageMessage=pageMessage, messageAttribute='deviceId', pageMessage=pageMessage, messageAttribute='deviceId',
throwReasons=[GAPI.INVALID_INPUT, GAPI.BAD_REQUEST, GAPI.INVALID_ORGUNIT, GAPI.FORBIDDEN], throwReasons=[GAPI.INVALID_INPUT, GAPI.BAD_REQUEST, GAPI.INVALID_ARGUMENT, GAPI.INVALID_ORGUNIT, GAPI.FORBIDDEN],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
customer=customerId, orgUnitPath=orgUnitPath, query=query, projection=projection, customer=customerId, orgUnitPath=orgUnitPath, query=query, projection=projection,
orderBy=orderBy, sortOrder=sortOrder, fields=fields) orderBy=orderBy, sortOrder=sortOrder, fields=fields)
@@ -25566,7 +25606,7 @@ def doPrintShowBrowsers():
else: else:
entityActionFailedWarning([Ent.CHROME_BROWSER, None], str(e)) entityActionFailedWarning([Ent.CHROME_BROWSER, None], str(e))
return return
except (GAPI.invalidOrgunit, GAPI.forbidden) as e: except (GAPI.invalidArgument, GAPI.invalidOrgunit, GAPI.forbidden) as e:
entityActionFailedWarning([Ent.CHROME_BROWSER, None], str(e)) entityActionFailedWarning([Ent.CHROME_BROWSER, None], str(e))
return return
except (GAPI.badRequest, GAPI.resourceNotFound): except (GAPI.badRequest, GAPI.resourceNotFound):
@@ -25574,15 +25614,17 @@ def doPrintShowBrowsers():
else: else:
sortRows = True sortRows = True
jcount = len(entityList) jcount = len(entityList)
fields = getFieldsFromFieldsList(fieldsList) fields = getFieldsFromFieldsList(fieldsList) if not rawFields else rawFields
j = 0 j = 0
for deviceId in entityList: for deviceId in entityList:
j += 1 j += 1
try: try:
browser = callGAPI(cbcm.chromebrowsers(), 'get', browser = callGAPI(cbcm.chromebrowsers(), 'get',
throwReasons=[GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN], throwReasons=[GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.FORBIDDEN],
customer=customerId, deviceId=deviceId, projection=projection, fields=fields) customer=customerId, deviceId=deviceId, projection=projection, fields=fields)
_printBrowser(browser) _printBrowser(browser)
except GAPI.invalidArgument as e:
entityActionFailedWarning([Ent.CHROME_BROWSER, deviceId], str(e))
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden): except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden):
checkEntityAFDNEorAccessErrorExit(None, Ent.CHROME_BROWSER, deviceId) checkEntityAFDNEorAccessErrorExit(None, Ent.CHROME_BROWSER, deviceId)
if csvPF: if csvPF:
@@ -31719,6 +31761,8 @@ def doCreateGroup(ciGroupsAPI=False):
'query': getString(Cmd.OB_QUERY)}) 'query': getString(Cmd.OB_QUERY)})
elif ciGroupsAPI and myarg == 'makeowner': elif ciGroupsAPI and myarg == 'makeowner':
initialGroupConfig = 'WITH_INITIAL_OWNER' initialGroupConfig = 'WITH_INITIAL_OWNER'
elif ciGroupsAPI and myarg in {'security', 'makesecuritygroup'}:
body['labels'][CIGROUP_SECURITY_LABEL] = ''
elif myarg == 'verifynotinvitable': elif myarg == 'verifynotinvitable':
verifyNotInvitable = True verifyNotInvitable = True
else: else:
@@ -34595,8 +34639,11 @@ def doPrintShowGroupTree():
if csvPF: if csvPF:
csvPF.writeCSVfile('Group Tree') csvPF.writeCSVfile('Group Tree')
# gam create cigroup <EmailAddress> [copyfrom <GroupItem>] <GroupAttribute> # gam create cigroup <EmailAddress>
# [makeowner] [alias|aliases <CIGroupAliasList>] [dynamic <QueryDynamicGroup>] # [copyfrom <GroupItem>] <GroupAttribute>
# [makeowner] [alias|aliases <CIGroupAliasList>]
# [security|makesecuritygroup]
# [dynamic <QueryDynamicGroup>]
def doCreateCIGroup(): def doCreateCIGroup():
doCreateGroup(ciGroupsAPI=True) doCreateGroup(ciGroupsAPI=True)
@@ -39150,10 +39197,12 @@ def _wipeCalendarEvents(user, origCal, calIds, count):
continue continue
try: try:
callGAPI(cal.calendars(), 'clear', callGAPI(cal.calendars(), 'clear',
throwReasons=GAPI.CALENDAR_THROW_REASONS+[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.INVALID, GAPI.REQUIRED_ACCESS_LEVEL], throwReasons=GAPI.CALENDAR_THROW_REASONS+[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.INVALID,
GAPI.REQUIRED_ACCESS_LEVEL, GAPI.SERVICE_NOT_AVAILABLE],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
calendarId=calId) calendarId=calId)
entityActionPerformed([Ent.CALENDAR, calId], i, count) entityActionPerformed([Ent.CALENDAR, calId], i, count)
except (GAPI.notFound, GAPI.forbidden, GAPI.invalid, GAPI.requiredAccessLevel) as e: except (GAPI.notFound, GAPI.forbidden, GAPI.invalid, GAPI.requiredAccessLevel, GAPI.serviceNotAvailable) as e:
entityActionFailedWarning([Ent.CALENDAR, calId], str(e), i, count) entityActionFailedWarning([Ent.CALENDAR, calId], str(e), i, count)
except GAPI.notACalendarUser: except GAPI.notACalendarUser:
userCalServiceNotEnabledWarning(calId, i, count) userCalServiceNotEnabledWarning(calId, i, count)
@@ -40410,9 +40459,10 @@ VAULT_SEARCH_METHODS_MAP = {
VAULT_CORPUS_ARGUMENT_MAP = { VAULT_CORPUS_ARGUMENT_MAP = {
'calendar': 'CALENDAR', 'calendar': 'CALENDAR',
'drive': 'DRIVE', 'drive': 'DRIVE',
'mail': 'MAIL', 'gemini': 'GEMINI',
'groups': 'GROUPS', 'groups': 'GROUPS',
'hangoutschat': 'HANGOUTS_CHAT', 'hangoutschat': 'HANGOUTS_CHAT',
'mail': 'MAIL',
'voice': 'VOICE', 'voice': 'VOICE',
} }
VAULT_COUNTS_CORPUS_ARGUMENT_MAP = { VAULT_COUNTS_CORPUS_ARGUMENT_MAP = {
@@ -40439,15 +40489,22 @@ VAULT_EXPORT_FORMAT_MAP = {
'ics': 'ICS', 'ics': 'ICS',
'mbox': 'MBOX', 'mbox': 'MBOX',
'pst': 'PST', 'pst': 'PST',
'xml': 'XML',
} }
VAULT_CORPUS_EXPORT_FORMATS = { VAULT_CORPUS_EXPORT_FORMATS = {
'CALENDAR': ['ICS', 'PST'], 'CALENDAR': ['ICS', 'PST'],
'DRIVE': [], 'DRIVE': [],
'GEMINI': ['XML'],
'GROUPS': ['MBOX', 'PST'], 'GROUPS': ['MBOX', 'PST'],
'HANGOUTS_CHAT': ['MBOX', 'PST'], 'HANGOUTS_CHAT': ['MBOX', 'PST'],
'MAIL': ['MBOX', 'PST'], 'MAIL': ['MBOX', 'PST'],
'VOICE' : ['MBOX', 'PST'], 'VOICE' : ['MBOX', 'PST'],
} }
VAULT_CSE_OPTION_MAP = {
'any': 'CLIENT_SIDE_ENCRYPTED_OPTION_ANY',
'encrypted': 'CLIENT_SIDE_ENCRYPTED_OPTION_ENCRYPTED',
'unencrypted': 'CLIENT_SIDE_ENCRYPTED_OPTION_UNENCRYPTED',
}
VAULT_EXPORT_REGION_MAP = { VAULT_EXPORT_REGION_MAP = {
'any': 'ANY', 'any': 'ANY',
'europe': 'EUROPE', 'europe': 'EUROPE',
@@ -40456,16 +40513,18 @@ VAULT_EXPORT_REGION_MAP = {
VAULT_CORPUS_OPTIONS_MAP = { VAULT_CORPUS_OPTIONS_MAP = {
'CALENDAR': 'calendarOptions', 'CALENDAR': 'calendarOptions',
'DRIVE': 'driveOptions', 'DRIVE': 'driveOptions',
'MAIL': 'mailOptions', 'GEMINI': 'geminiOptions',
'GROUPS': 'groupsOptions', 'GROUPS': 'groupsOptions',
'HANGOUTS_CHAT': 'hangoutsChatOptions', 'HANGOUTS_CHAT': 'hangoutsChatOptions',
'MAIL': 'mailOptions',
'VOICE': 'voiceOptions', 'VOICE': 'voiceOptions',
} }
VAULT_CORPUS_QUERY_MAP = { VAULT_CORPUS_QUERY_MAP = {
'CALENDAR': None, 'CALENDAR': None,
'DRIVE': 'driveQuery', 'DRIVE': 'driveQuery',
'MAIL': 'mailQuery', 'GEMINI': None,
'GROUPS': 'groupsQuery', 'GROUPS': 'groupsQuery',
'MAIL': 'mailQuery',
'HANGOUTS_CHAT': 'hangoutsChatQuery', 'HANGOUTS_CHAT': 'hangoutsChatQuery',
'VOICE': 'voiceQuery', 'VOICE': 'voiceQuery',
} }
@@ -40474,11 +40533,11 @@ VAULT_QUERY_ARGS = [
# calendar # calendar
'locationquery', 'peoplequery', 'minuswords', 'responsestatuses', 'caldendarversiondate', 'locationquery', 'peoplequery', 'minuswords', 'responsestatuses', 'caldendarversiondate',
# drive # drive
'driveversiondate', 'includeshareddrives', 'includeteamdrives', 'driveclientsideencryption', 'driveversiondate', 'includeshareddrives', 'includeteamdrives',
# hangoutsChat # hangoutsChat
'includerooms', 'includerooms',
# mail # mail
'excludedrafts', 'mailclientsideencryption', 'excludedrafts',
# voice # voice
'covereddata', 'covereddata',
] + list(VAULT_SEARCH_METHODS_MAP.keys()) ] + list(VAULT_SEARCH_METHODS_MAP.keys())
@@ -40535,12 +40594,16 @@ def _buildVaultQuery(myarg, query, corpusArgumentMap):
query.setdefault('driveOptions', {})['versionDate'] = getTimeOrDeltaFromNow() query.setdefault('driveOptions', {})['versionDate'] = getTimeOrDeltaFromNow()
elif myarg in {'includeshareddrives', 'includeteamdrives'}: elif myarg in {'includeshareddrives', 'includeteamdrives'}:
query.setdefault('driveOptions', {})['includeSharedDrives'] = getBoolean() query.setdefault('driveOptions', {})['includeSharedDrives'] = getBoolean()
elif myarg == 'driveclientsideencryption':
query.setdefault('driveOptions', {})['clientSideEncryptedOption'] = getChoice(VAULT_CSE_OPTION_MAP, mapChoice=True)
# hangoutsChat # hangoutsChat
elif myarg == 'includerooms': elif myarg == 'includerooms':
query['hangoutsChatOptions'] = {'includeRooms': getBoolean()} query['hangoutsChatOptions'] = {'includeRooms': getBoolean()}
# mail # mail
elif myarg == 'excludedrafts': elif myarg == 'excludedrafts':
query['mailOptions'] = {'excludeDrafts': getBoolean()} query['mailOptions'] = {'excludeDrafts': getBoolean()}
elif myarg == 'mailclientsideencryption':
query.setdefault('mailOptions', {})['clientSideEncryptedOption'] = getChoice(VAULT_CSE_OPTION_MAP, mapChoice=True)
# voice # voice
elif myarg == 'covereddata': elif myarg == 'covereddata':
query['voiceOptions'] = {'coveredData': getChoice(VAULT_VOICE_COVERED_DATA_MAP, mapChoice=True)} query['voiceOptions'] = {'coveredData': getChoice(VAULT_VOICE_COVERED_DATA_MAP, mapChoice=True)}
@@ -40555,7 +40618,7 @@ def _validateVaultQuery(body, corpusArgumentMap):
if body['query']['corpus'] != corpus: if body['query']['corpus'] != corpus:
body['exportOptions'].pop(options, None) body['exportOptions'].pop(options, None)
# gam create vaultexport|export matter <MatterItem> [name <String>] corpus calendar|drive|mail|groups|hangouts_chat|voice # gam create vaultexport|export matter <MatterItem> [name <String>] corpus calendar|drive|gemini|groups|hangouts_chat|mail|voice
# (accounts <EmailAddressEntity>) | (orgunit|org|ou <OrgUnitPath>) | everyone # (accounts <EmailAddressEntity>) | (orgunit|org|ou <OrgUnitPath>) | everyone
# (shareddrives|teamdrives <TeamDriveIDList>) | (rooms <RoomList>) | (sitesurl <URLList>) # (shareddrives|teamdrives <TeamDriveIDList>) | (rooms <RoomList>) | (sitesurl <URLList>)
# [scope <all_data|held_data|unprocessed_data>] # [scope <all_data|held_data|unprocessed_data>]
@@ -40563,10 +40626,12 @@ def _validateVaultQuery(body, corpusArgumentMap):
# [locationquery <StringList>] [peoplequery <StringList>] [minuswords <StringList>] # [locationquery <StringList>] [peoplequery <StringList>] [minuswords <StringList>]
# [responsestatuses <AttendeeStatus>(,<AttendeeStatus>)*] [calendarversiondate <Date>|<Time>] # [responsestatuses <AttendeeStatus>(,<AttendeeStatus>)*] [calendarversiondate <Date>|<Time>]
# [includeshareddrives <Boolean>] [driveversiondate <Date>|<Time>] [includeaccessinfo <Boolean>] # [includeshareddrives <Boolean>] [driveversiondate <Date>|<Time>] [includeaccessinfo <Boolean>]
# [driveclientsideencryption any|encrypted|unencrypted]
# [includerooms <Boolean>] # [includerooms <Boolean>]
# [excludedrafts <Boolean>] [format mbox|pst] # [excludedrafts <Boolean>] [mailclientsideencryption any|encrypted|unencrypted]
# [showconfidentialmodecontent <Boolean>] [usenewexport <Boolean>] [exportlinkeddrivefiles <Boolean>] # [showconfidentialmodecontent <Boolean>] [usenewexport <Boolean>] [exportlinkeddrivefiles <Boolean>]
# [covereddata calllogs|textmessages|voicemails] # [covereddata calllogs|textmessages|voicemails]
# [format ics|mbox|pst|xml]
# [region any|europe|us] [showdetails|returnidonly] # [region any|europe|us] [showdetails|returnidonly]
def doCreateVaultExport(): def doCreateVaultExport():
v = buildGAPIObject(API.VAULT) v = buildGAPIObject(API.VAULT)
@@ -41600,23 +41665,22 @@ def printShowUserVaultHolds(entityList):
else: else:
printKeyValueList(['Total Holds', totalHolds]) printKeyValueList(['Total Holds', totalHolds])
def _cleanVaultQuery(query, cd, drive): def _cleanVaultQuery(query, cd):
if 'query' in query: if 'query' in query:
if cd is not None: if cd is not None:
if 'orgUnitInfo' in query['query']: if 'orgUnitInfo' in query['query']:
query['query']['orgUnitInfo']['orgUnitPath'] = convertOrgUnitIDtoPath(cd, query['query']['orgUnitInfo']['orgUnitId']) query['query']['orgUnitInfo']['orgUnitPath'] = convertOrgUnitIDtoPath(cd, query['query']['orgUnitInfo']['orgUnitId'])
if drive is not None:
if 'sharedDriveInfo' in query['query']: if 'sharedDriveInfo' in query['query']:
query['query']['sharedDriveInfo']['sharedDriveNames'] = [] query['query']['sharedDriveInfo']['sharedDriveNames'] = []
for sharedDriveId in query['query']['sharedDriveInfo']['sharedDriveIds']: for sharedDriveId in query['query']['sharedDriveInfo']['sharedDriveIds']:
query['query']['sharedDriveInfo']['sharedDriveNames'].append(_getSharedDriveNameFromId(drive, sharedDriveId, useDomainAdminAccess=True)) query['query']['sharedDriveInfo']['sharedDriveNames'].append(_getSharedDriveNameFromId(sharedDriveId))
query['query'].pop('searchMethod', None) query['query'].pop('searchMethod', None)
query['query'].pop('teamDriveInfo', None) query['query'].pop('teamDriveInfo', None)
VAULT_QUERY_TIME_OBJECTS = {'createTime', 'endTime', 'startTime', 'versionDate'} VAULT_QUERY_TIME_OBJECTS = {'createTime', 'endTime', 'startTime', 'versionDate'}
def _showVaultQuery(matterNameId, query, cd, drive, FJQC, k=0, kcount=0): def _showVaultQuery(matterNameId, query, cd, FJQC, k=0, kcount=0):
_cleanVaultQuery(query, cd, drive) _cleanVaultQuery(query, cd)
if FJQC is not None and FJQC.formatJSON: if FJQC is not None and FJQC.formatJSON:
printLine(json.dumps(cleanJSON(query, timeObjects=VAULT_QUERY_TIME_OBJECTS), ensure_ascii=False, sort_keys=False)) printLine(json.dumps(cleanJSON(query, timeObjects=VAULT_QUERY_TIME_OBJECTS), ensure_ascii=False, sort_keys=False))
return return
@@ -41648,7 +41712,7 @@ def doInfoVaultQuery():
queryId, queryName, queryNameId = convertQueryNameToID(v, getString(Cmd.OB_QUERY_ITEM), matterId, matterNameId) queryId, queryName, queryNameId = convertQueryNameToID(v, getString(Cmd.OB_QUERY_ITEM), matterId, matterNameId)
else: else:
queryName = getString(Cmd.OB_QUERY_ITEM) queryName = getString(Cmd.OB_QUERY_ITEM)
cd = drive = None cd = None
fieldsList = [] fieldsList = []
FJQC = FormatJSONQuoteChar() FJQC = FormatJSONQuoteChar()
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
@@ -41658,8 +41722,8 @@ def doInfoVaultQuery():
queryId, queryName, queryNameId = convertQueryNameToID(v, queryName, matterId, matterNameId) queryId, queryName, queryNameId = convertQueryNameToID(v, queryName, matterId, matterNameId)
elif myarg == 'shownames': elif myarg == 'shownames':
cd = buildGAPIObject(API.DIRECTORY) cd = buildGAPIObject(API.DIRECTORY)
_, drive = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail()) _, GM.Globals[GM.ADMIN_DRIVE] = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail())
if drive is None: if GM.Globals[GM.ADMIN_DRIVE] is None:
return return
elif getFieldsList(myarg, VAULT_QUERY_FIELDS_CHOICE_MAP, fieldsList, initialField=['savedQueryId', 'displayName']): elif getFieldsList(myarg, VAULT_QUERY_FIELDS_CHOICE_MAP, fieldsList, initialField=['savedQueryId', 'displayName']):
pass pass
@@ -41670,7 +41734,7 @@ def doInfoVaultQuery():
query = callGAPI(v.matters().savedQueries(), 'get', query = callGAPI(v.matters().savedQueries(), 'get',
throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN], throwReasons=[GAPI.NOT_FOUND, GAPI.BAD_REQUEST, GAPI.FORBIDDEN],
matterId=matterId, savedQueryId=queryId, fields=fields) matterId=matterId, savedQueryId=queryId, fields=fields)
_showVaultQuery(matterNameId, query, cd, drive, FJQC) _showVaultQuery(matterNameId, query, cd, FJQC)
except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden) as e: except (GAPI.notFound, GAPI.badRequest, GAPI.forbidden) as e:
entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_QUERY, queryNameId], str(e)) entityActionFailedWarning([Ent.VAULT_MATTER, matterNameId, Ent.VAULT_QUERY, queryNameId], str(e))
@@ -41687,7 +41751,7 @@ def doPrintShowVaultQueries():
csvPF = CSVPrintFile(PRINT_VAULT_QUERIES_TITLES, 'sortall') if Act.csvFormat() else None csvPF = CSVPrintFile(PRINT_VAULT_QUERIES_TITLES, 'sortall') if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF) FJQC = FormatJSONQuoteChar(csvPF)
matters = [] matters = []
cd = drive = None cd = None
fieldsList = [] fieldsList = []
while Cmd.ArgumentsRemaining(): while Cmd.ArgumentsRemaining():
myarg = getArgument() myarg = getArgument()
@@ -41697,8 +41761,8 @@ def doPrintShowVaultQueries():
matters = shlexSplitList(getString(Cmd.OB_MATTER_ITEM_LIST)) matters = shlexSplitList(getString(Cmd.OB_MATTER_ITEM_LIST))
elif myarg == 'shownames': elif myarg == 'shownames':
cd = buildGAPIObject(API.DIRECTORY) cd = buildGAPIObject(API.DIRECTORY)
_, drive = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail()) _, GM.Globals[GM.ADMIN_DRIVE] = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail())
if drive is None: if GM.Globals[GM.ADMIN_DRIVE] is None:
return return
elif getFieldsList(myarg, VAULT_QUERY_FIELDS_CHOICE_MAP, fieldsList, initialField=['savedQueryId', 'displayName']): elif getFieldsList(myarg, VAULT_QUERY_FIELDS_CHOICE_MAP, fieldsList, initialField=['savedQueryId', 'displayName']):
pass pass
@@ -41760,11 +41824,11 @@ def doPrintShowVaultQueries():
k = 0 k = 0
for query in queries: for query in queries:
k += 1 k += 1
_showVaultQuery(matterNameId, query, cd, drive, FJQC, k, kcount) _showVaultQuery(matterNameId, query, cd, FJQC, k, kcount)
Ind.Decrement() Ind.Decrement()
else: else:
for query in queries: for query in queries:
_cleanVaultQuery(query, cd, drive) _cleanVaultQuery(query, cd)
row = flattenJSON(query, flattened={'matterId': matterId, 'matterName': matterName}, timeObjects=VAULT_QUERY_TIME_OBJECTS) row = flattenJSON(query, flattened={'matterId': matterId, 'matterName': matterName}, timeObjects=VAULT_QUERY_TIME_OBJECTS)
if not FJQC.formatJSON: if not FJQC.formatJSON:
csvPF.WriteRowTitles(row) csvPF.WriteRowTitles(row)
@@ -48226,7 +48290,7 @@ def doPrintCourseTopics():
jcount = len(courseTopicIds) jcount = len(courseTopicIds)
if jcount == 0: if jcount == 0:
continue continue
fields = f'{",".join(set(fieldsList))}' fields = getFieldsFromFieldsList(fieldsList)
j = 0 j = 0
for courseTopicId in courseTopicIds: for courseTopicId in courseTopicIds:
j += 1 j += 1
@@ -48461,7 +48525,7 @@ def doPrintCourseWM(entityIDType, entityStateType):
jcount = len(courseWMIds) jcount = len(courseWMIds)
if jcount == 0: if jcount == 0:
continue continue
fields = f'{",".join(set(fieldsList))}' if fieldsList else None fields = getFieldsFromFieldsList(fieldsList)
j = 0 j = 0
for courseWMId in courseWMIds: for courseWMId in courseWMIds:
j += 1 j += 1
@@ -52756,16 +52820,21 @@ def _convertSharedDriveNameToId(drive, user, i, count, fileIdEntity, useDomainAd
','.join([td['id'] for td in tdlist])), i, count) ','.join([td['id'] for td in tdlist])), i, count)
return False return False
def _getSharedDriveNameFromId(drive, sharedDriveId, useDomainAdminAccess=False): def _getSharedDriveNameFromId(sharedDriveId):
sharedDriveName = GM.Globals[GM.MAP_SHAREDDRIVE_ID_TO_NAME].get(sharedDriveId) sharedDriveName = GM.Globals[GM.MAP_SHAREDDRIVE_ID_TO_NAME].get(sharedDriveId)
if not sharedDriveName: if not sharedDriveName:
if not GM.Globals[GM.ADMIN_DRIVE]:
_, GM.Globals[GM.ADMIN_DRIVE] = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail())
if GM.Globals[GM.ADMIN_DRIVE]:
try: try:
sharedDriveName = callGAPI(drive.drives(), 'get', sharedDriveName = callGAPI(GM.Globals[GM.ADMIN_DRIVE].drives(), 'get',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.NOT_FOUND], throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.NOT_FOUND],
useDomainAdminAccess=useDomainAdminAccess, useDomainAdminAccess=True,
driveId=sharedDriveId, fields='name')['name'] driveId=sharedDriveId, fields='name')['name']
except (GAPI.notFound, GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy): except (GAPI.notFound, GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy):
sharedDriveName = TEAM_DRIVE sharedDriveName = TEAM_DRIVE
else:
sharedDriveName = TEAM_DRIVE
GM.Globals[GM.MAP_SHAREDDRIVE_ID_TO_NAME][sharedDriveId] = sharedDriveName GM.Globals[GM.MAP_SHAREDDRIVE_ID_TO_NAME][sharedDriveId] = sharedDriveName
return sharedDriveName return sharedDriveName
@@ -52777,7 +52846,7 @@ def _getDriveFileNameFromId(drive, fileId, combineTitleId=True, useDomainAdminAc
if result: if result:
fileName = result['name'] fileName = result['name']
if (result['mimeType'] == MIMETYPE_GA_FOLDER) and result.get('driveId') and (result['name'] == TEAM_DRIVE): if (result['mimeType'] == MIMETYPE_GA_FOLDER) and result.get('driveId') and (result['name'] == TEAM_DRIVE):
fileName = _getSharedDriveNameFromId(drive, result['driveId']) fileName = _getSharedDriveNameFromId(result['driveId'])
if combineTitleId: if combineTitleId:
fileName += '('+fileId+')' fileName += '('+fileId+')'
return (fileName, _getEntityMimeType(result), result['mimeType']) return (fileName, _getEntityMimeType(result), result['mimeType'])
@@ -53859,7 +53928,7 @@ def getFilePaths(drive, fileTree, initialResult, filePathInfo, addParentsToTree=
fullpath=False, showDepth=False, folderPathOnly=False): fullpath=False, showDepth=False, folderPathOnly=False):
def _getParentName(result): def _getParentName(result):
if (result['mimeType'] == MIMETYPE_GA_FOLDER) and result.get('driveId') and (result['name'] == TEAM_DRIVE): if (result['mimeType'] == MIMETYPE_GA_FOLDER) and result.get('driveId') and (result['name'] == TEAM_DRIVE):
parentName = _getSharedDriveNameFromId(drive, result['driveId']) parentName = _getSharedDriveNameFromId(result['driveId'])
if parentName != TEAM_DRIVE: if parentName != TEAM_DRIVE:
return f'{SHARED_DRIVES}{filePathInfo["delimiter"]}{parentName}' return f'{SHARED_DRIVES}{filePathInfo["delimiter"]}{parentName}'
return result['name'] return result['name']
@@ -54558,9 +54627,9 @@ def showFileInfo(users):
driveId = result.get('driveId') driveId = result.get('driveId')
if driveId: if driveId:
if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE: if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE:
result['name'] = _getSharedDriveNameFromId(drive, driveId) result['name'] = _getSharedDriveNameFromId(driveId)
if DFF.showSharedDriveNames: if DFF.showSharedDriveNames:
result['driveName'] = _getSharedDriveNameFromId(drive, driveId) result['driveName'] = _getSharedDriveNameFromId(driveId)
if showNoParents: if showNoParents:
result.setdefault('parents', []) result.setdefault('parents', [])
if getPermissionsForSharedDrives and driveId and 'permissions' not in result: if getPermissionsForSharedDrives and driveId and 'permissions' not in result:
@@ -55248,7 +55317,7 @@ def extendFileTreeParents(drive, fileTree, fields):
result['parents'] = [ORPHANS] if result.get('ownedByMe', False) and 'sharedWithMeTime' not in result else [SHARED_WITHME] result['parents'] = [ORPHANS] if result.get('ownedByMe', False) and 'sharedWithMeTime' not in result else [SHARED_WITHME]
else: else:
if result['name'] == TEAM_DRIVE: if result['name'] == TEAM_DRIVE:
result['name'] = _getSharedDriveNameFromId(drive, result['driveId']) result['name'] = _getSharedDriveNameFromId(result['driveId'])
result['parents'] = [SHARED_DRIVES] if 'sharedWithMeTime' not in result else [SHARED_WITHME] result['parents'] = [SHARED_DRIVES] if 'sharedWithMeTime' not in result else [SHARED_WITHME]
fileTree[fileId]['info'] = result fileTree[fileId]['info'] = result
fileTree[fileId]['info']['noDisplay'] = True fileTree[fileId]['info']['noDisplay'] = True
@@ -55987,7 +56056,7 @@ def printFileList(users):
if not pmselect and 'permissions' in fileInfo: if not pmselect and 'permissions' in fileInfo:
fileInfo['permissions'] = DLP.GetFileMatchingPermission(fileInfo) fileInfo['permissions'] = DLP.GetFileMatchingPermission(fileInfo)
if DFF.showSharedDriveNames and driveId: if DFF.showSharedDriveNames and driveId:
fileInfo['driveName'] = _getSharedDriveNameFromId(drive, driveId) fileInfo['driveName'] = _getSharedDriveNameFromId(driveId)
if filepath: if filepath:
if not FJQC.formatJSON or not addPathsToJSON: if not FJQC.formatJSON or not addPathsToJSON:
addFilePathsToRow(drive, fileTree, fileInfo, filePathInfo, csvPF, row, addFilePathsToRow(drive, fileTree, fileInfo, filePathInfo, csvPF, row,
@@ -56042,7 +56111,7 @@ def printFileList(users):
else: else:
if not countsRowFilter: if not countsRowFilter:
csvPFco.UpdateMimeTypeCounts(flattenJSON(fileInfo, flattened=row, skipObjects=skipObjects, timeObjects=timeObjects, csvPFco.UpdateMimeTypeCounts(flattenJSON(fileInfo, flattened=row, skipObjects=skipObjects, timeObjects=timeObjects,
simpleLists=simpleLists, delimiter=delimiter), mimeTypeInfo) simpleLists=simpleLists, delimiter=delimiter), mimeTypeInfo, sizeField)
else: else:
mimeTypeInfo.setdefault(fileInfo['mimeType'], {'count': 0, 'size': 0}) mimeTypeInfo.setdefault(fileInfo['mimeType'], {'count': 0, 'size': 0})
mimeTypeInfo[fileInfo['mimeType']]['count'] += 1 mimeTypeInfo[fileInfo['mimeType']]['count'] += 1
@@ -56863,7 +56932,7 @@ def printShowFilePaths(users):
driveId = result.get('driveId') driveId = result.get('driveId')
if driveId: if driveId:
if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE: if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE:
result['name'] = _getSharedDriveNameFromId(drive, driveId) result['name'] = _getSharedDriveNameFromId(driveId)
if returnPathOnly: if returnPathOnly:
if fullpath: if fullpath:
writeStdout(f'{SHARED_DRIVES}/{result["name"]}\n') writeStdout(f'{SHARED_DRIVES}/{result["name"]}\n')
@@ -56953,7 +57022,7 @@ def printFileParentTree(users):
driveId = result.get('driveId') driveId = result.get('driveId')
if driveId: if driveId:
if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE: if result['mimeType'] == MIMETYPE_GA_FOLDER and result['name'] == TEAM_DRIVE:
result['name'] = _getSharedDriveNameFromId(drive, driveId) result['name'] = _getSharedDriveNameFromId(driveId)
result['isRoot'] = True result['isRoot'] = True
result['parents'] = [''] result['parents'] = ['']
fileList.append(result) fileList.append(result)
@@ -57151,10 +57220,7 @@ def printShowFileCounts(users):
if not drive: if not drive:
continue continue
sharedDriveId = fileIdEntity.get('shareddrive', {}).get('driveId', '') sharedDriveId = fileIdEntity.get('shareddrive', {}).get('driveId', '')
if sharedDriveId: sharedDriveName = _getSharedDriveNameFromId(sharedDriveId) if sharedDriveId else ''
sharedDriveName = _getSharedDriveNameFromId(drive, sharedDriveId)
else:
sharedDriveName = ''
mimeTypeInfo = {} mimeTypeInfo = {}
userLastModification = { userLastModification = {
'lastModifiedFileId': '', 'lastModifiedFileName': '', 'lastModifiedFileId': '', 'lastModifiedFileName': '',
@@ -57383,7 +57449,7 @@ def printDiskUsage(users):
includeOwner = False includeOwner = False
csvPF.RemoveTitles(['Owner', 'ownedByMe']) csvPF.RemoveTitles(['Owner', 'ownedByMe'])
if topFolder['name'] == TEAM_DRIVE and not topFolder.get('parents'): if topFolder['name'] == TEAM_DRIVE and not topFolder.get('parents'):
topFolder['name'] = _getSharedDriveNameFromId(drive, driveId) topFolder['name'] = _getSharedDriveNameFromId(driveId)
topFolder['path'] = f'{SHARED_DRIVES}{pathDelimiter}{topFolder["name"]}' topFolder['path'] = f'{SHARED_DRIVES}{pathDelimiter}{topFolder["name"]}'
else: else:
topFolder['path'] = topFolder['name'] topFolder['path'] = topFolder['name']
@@ -59448,7 +59514,7 @@ def _getCopyMoveParentInfo(drive, user, i, count, j, jcount, newParentId, statis
result['destParentType'] = DEST_PARENT_MYDRIVE_FOLDER result['destParentType'] = DEST_PARENT_MYDRIVE_FOLDER
else: else:
if result['name'] == TEAM_DRIVE and not result.get('parents', []): if result['name'] == TEAM_DRIVE and not result.get('parents', []):
result['name'] = _getSharedDriveNameFromId(drive, result['driveId']) result['name'] = _getSharedDriveNameFromId(result['driveId'])
result['destParentType'] = DEST_PARENT_SHAREDDRIVE_ROOT result['destParentType'] = DEST_PARENT_SHAREDDRIVE_ROOT
else: else:
result['destParentType'] = DEST_PARENT_SHAREDDRIVE_FOLDER result['destParentType'] = DEST_PARENT_SHAREDDRIVE_FOLDER
@@ -59994,7 +60060,7 @@ def copyDriveFile(users):
# Source at root of Shared Drive? # Source at root of Shared Drive?
sourceMimeType = source['mimeType'] sourceMimeType = source['mimeType']
if sourceMimeType == MIMETYPE_GA_FOLDER and source.get('driveId') and source['name'] == TEAM_DRIVE and not source.get('parents', []): if sourceMimeType == MIMETYPE_GA_FOLDER and source.get('driveId') and source['name'] == TEAM_DRIVE and not source.get('parents', []):
source['name'] = _getSharedDriveNameFromId(drive, source['driveId']) source['name'] = _getSharedDriveNameFromId(source['driveId'])
sourceName = source['name'] sourceName = source['name']
sourceNameId = f"{sourceName}({source['id']})" sourceNameId = f"{sourceName}({source['id']})"
copyMoveOptions['sourceDriveId'] = source.get('driveId') copyMoveOptions['sourceDriveId'] = source.get('driveId')
@@ -60762,7 +60828,7 @@ def moveDriveFile(users):
if sourceMimeType == MIMETYPE_GA_FOLDER and source['name'] in [MY_DRIVE, TEAM_DRIVE] and not source.get('parents', []): if sourceMimeType == MIMETYPE_GA_FOLDER and source['name'] in [MY_DRIVE, TEAM_DRIVE] and not source.get('parents', []):
copyMoveOptions['sourceIsMyDriveSharedDrive'] = True copyMoveOptions['sourceIsMyDriveSharedDrive'] = True
if source.get('driveId'): if source.get('driveId'):
source['name'] = _getSharedDriveNameFromId(drive, source['driveId']) source['name'] = _getSharedDriveNameFromId(source['driveId'])
sourceName = source['name'] sourceName = source['name']
sourceNameId = f"{sourceName}({source['id']})" sourceNameId = f"{sourceName}({source['id']})"
copyMoveOptions['sourceDriveId'] = source.get('driveId') copyMoveOptions['sourceDriveId'] = source.get('driveId')
@@ -63187,7 +63253,7 @@ def printEmptyDriveFolders(users):
fileIdEntity['shareddrive'] = {'driveId': sharedDriveId, 'corpora': 'drive', 'includeItemsFromAllDrives': True, 'supportsAllDrives': True} fileIdEntity['shareddrive'] = {'driveId': sharedDriveId, 'corpora': 'drive', 'includeItemsFromAllDrives': True, 'supportsAllDrives': True}
csvPF.AddTitles(['driveId']) csvPF.AddTitles(['driveId'])
csvPF.MoveTitlesToEnd(['name']) csvPF.MoveTitlesToEnd(['name'])
pathList = [f'{SHARED_DRIVES}/{_getSharedDriveNameFromId(drive, sharedDriveId)}'] pathList = [f'{SHARED_DRIVES}/{_getSharedDriveNameFromId(sharedDriveId)}']
else: else:
pathList = [fileEntryInfo['name']] pathList = [fileEntryInfo['name']]
mimeType = fileEntryInfo['mimeType'] mimeType = fileEntryInfo['mimeType']
@@ -63277,7 +63343,7 @@ def deleteEmptyDriveFolders(users):
if 'driveId' in fileEntryInfo: if 'driveId' in fileEntryInfo:
sharedDriveId = fileEntryInfo['driveId'] sharedDriveId = fileEntryInfo['driveId']
fileIdEntity['shareddrive'] = {'driveId': sharedDriveId, 'corpora': 'drive', 'includeItemsFromAllDrives': True, 'supportsAllDrives': True} fileIdEntity['shareddrive'] = {'driveId': sharedDriveId, 'corpora': 'drive', 'includeItemsFromAllDrives': True, 'supportsAllDrives': True}
pathList = [f'{SHARED_DRIVES}/{_getSharedDriveNameFromId(drive, sharedDriveId)}'] pathList = [f'{SHARED_DRIVES}/{_getSharedDriveNameFromId(sharedDriveId)}']
else: else:
pathList = [fileEntryInfo['name']] pathList = [fileEntryInfo['name']]
mimeType = fileEntryInfo['mimeType'] mimeType = fileEntryInfo['mimeType']
@@ -65481,7 +65547,7 @@ def infoSharedDrive(users, useDomainAdminAccess=False):
guiRoles = getBoolean() guiRoles = getBoolean()
else: else:
FJQC.GetFormatJSON(myarg) FJQC.GetFormatJSON(myarg)
fields = ','.join(set(fieldsList)) if fieldsList else '*' fields = getFieldsFromFieldsList(fieldsList) if fieldsList else '*'
i, count, users = getEntityArgument(users) i, count, users = getEntityArgument(users)
for user in users: for user in users:
i += 1 i += 1
@@ -65693,7 +65759,7 @@ def doPrintShowSharedDrives():
def doPrintShowOrgunitSharedDrives(): def doPrintShowOrgunitSharedDrives():
def _getOrgUnitSharedDriveInfo(shareddrive): def _getOrgUnitSharedDriveInfo(shareddrive):
shareddrive['driveId'] = shareddrive['name'].rsplit(';')[1] shareddrive['driveId'] = shareddrive['name'].rsplit(';')[1]
shareddrive['driveName'] = _getSharedDriveNameFromId(drive, shareddrive['driveId'], useDomainAdminAccess=True) shareddrive['driveName'] = _getSharedDriveNameFromId(shareddrive['driveId'])
shareddrive['orgUnitPath'] = orgUnitPath shareddrive['orgUnitPath'] = orgUnitPath
def _showOrgUnitSharedDrive(shareddrive, j, jcount, FJQC): def _showOrgUnitSharedDrive(shareddrive, j, jcount, FJQC):
@@ -65712,8 +65778,8 @@ def doPrintShowOrgunitSharedDrives():
ci = buildGAPIObject(API.CLOUDIDENTITY_ORGUNITS_BETA) ci = buildGAPIObject(API.CLOUDIDENTITY_ORGUNITS_BETA)
cd = buildGAPIObject(API.DIRECTORY) cd = buildGAPIObject(API.DIRECTORY)
_, drive = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail()) _, GM.Globals[GM.ADMIN_DRIVE] = buildGAPIServiceObject(API.DRIVE3, _getAdminEmail())
if not drive: if not GM.Globals[GM.ADMIN_DRIVE]:
return return
csvPF = CSVPrintFile(['name', 'type', 'member', 'memberUri', 'driveId', 'driveName', 'orgUnitPath']) if Act.csvFormat() else None csvPF = CSVPrintFile(['name', 'type', 'member', 'memberUri', 'driveId', 'driveName', 'orgUnitPath']) if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF) FJQC = FormatJSONQuoteChar(csvPF)
@@ -65792,13 +65858,13 @@ def copySyncSharedDriveACLs(users, useDomainAdminAccess=False):
if not drive: if not drive:
continue continue
if not srcFileIdEntity.get('shareddrivename'): if not srcFileIdEntity.get('shareddrivename'):
srcFileIdEntity['shareddrivename'] = _getSharedDriveNameFromId(drive, srcFileIdEntity['shareddrive']['driveId']) srcFileIdEntity['shareddrivename'] = _getSharedDriveNameFromId(srcFileIdEntity['shareddrive']['driveId'])
if tgtFileIdEntity.get('shareddrivename'): if tgtFileIdEntity.get('shareddrivename'):
if not _convertSharedDriveNameToId(drive, user, i, count, tgtFileIdEntity, useDomainAdminAccess): if not _convertSharedDriveNameToId(drive, user, i, count, tgtFileIdEntity, useDomainAdminAccess):
continue continue
tgtFileIdEntity['shareddrive']['corpora'] = 'drive' tgtFileIdEntity['shareddrive']['corpora'] = 'drive'
else: else:
tgtFileIdEntity['shareddrivename'] = _getSharedDriveNameFromId(drive, tgtFileIdEntity['shareddrive']['driveId']) tgtFileIdEntity['shareddrivename'] = _getSharedDriveNameFromId(tgtFileIdEntity['shareddrive']['driveId'])
statistics = _initStatistics() statistics = _initStatistics()
copyMoveOptions['sourceDriveId'] = srcFileIdEntity['shareddrive']['driveId'] copyMoveOptions['sourceDriveId'] = srcFileIdEntity['shareddrive']['driveId']
copyMoveOptions['destDriveId'] = tgtFileIdEntity['shareddrive']['driveId'] copyMoveOptions['destDriveId'] = tgtFileIdEntity['shareddrive']['driveId']
@@ -69387,13 +69453,18 @@ LABEL_COUNTS_FIELDS = ','.join(LABEL_COUNTS_FIELDS_LIST)
def printShowLabels(users): def printShowLabels(users):
def _buildLabelTree(labels): def _buildLabelTree(labels):
def _checkChildLabel(label): def _checkChildLabel(label):
if label.find('/') != -1: labelItemList = label.split('/')
(parent, base) = label.rsplit('/', 1) i = len(labelItemList)-1
while i > 0:
parent = '/'.join(labelItemList[:i])
base = '/'.join(labelItemList[i:])
if parent in labelTree: if parent in labelTree:
if label in labelTree: if label in labelTree:
labelTree[label]['info']['base'] = base labelTree[label]['info']['base'] = base
labelTree[parent]['children'].append(labelTree.pop(label)) labelTree[parent]['children'].append(labelTree.pop(label))
_checkChildLabel(parent) _checkChildLabel(parent)
return
i -= 1
labelTree = {} labelTree = {}
for label in labels['labels']: for label in labels['labels']:
@@ -69626,7 +69697,7 @@ def _initMessageThreadParameters(entityType, doIt, maxToProcess):
'query': '', 'queryTimes': {}, 'query': '', 'queryTimes': {},
'entityType': entityType, 'messageEntity': None, 'doIt': doIt, 'quick': True, 'entityType': entityType, 'messageEntity': None, 'doIt': doIt, 'quick': True,
'labelMatchPattern': None, 'senderMatchPattern': None, 'labelMatchPattern': None, 'senderMatchPattern': None,
'maxToProcess': maxToProcess, 'maxItems': 0, 'maxToProcess': maxToProcess, 'maxItems': 0, 'maxMessagesPerThread': 0,
'maxToKeywords': [MESSAGES_MAX_TO_KEYWORDS[Act.Get()], 'maxtoprocess'], 'maxToKeywords': [MESSAGES_MAX_TO_KEYWORDS[Act.Get()], 'maxtoprocess'],
'listType': listType, 'fields': f'nextPageToken,{listType}(id)'} 'listType': listType, 'fields': f'nextPageToken,{listType}(id)'}
@@ -69668,6 +69739,8 @@ def _getMessageSelectParameters(myarg, parameters):
parameters['doIt'] = True parameters['doIt'] = True
elif myarg in parameters['maxToKeywords']: elif myarg in parameters['maxToKeywords']:
parameters['maxToProcess'] = getInteger(minVal=0) parameters['maxToProcess'] = getInteger(minVal=0)
elif myarg == 'maxmessagesperthread':
parameters['maxMessagesPerThread'] = getInteger(minVal=0)
else: else:
return False return False
return True return True
@@ -70807,8 +70880,6 @@ def printShowMessagesThreads(users, entityType):
return None return None
def _qualifyMessage(user, result): def _qualifyMessage(user, result):
if parameters['maxToProcess'] and parameters['messagesProcessed'] == parameters['maxToProcess']:
return (False, None)
if senderMatchPattern: if senderMatchPattern:
sender = _checkSenderMatchCount(result) sender = _checkSenderMatchCount(result)
if not sender: if not sender:
@@ -70836,7 +70907,9 @@ def printShowMessagesThreads(users, entityType):
except ValueError: except ValueError:
return headerValue return headerValue
def _showMessage(user, result, j, jcount): def _showMessage(user, result, j, jcount, checkMax=True):
if checkMax and parameters['maxToProcess'] and parameters['messagesProcessed'] == parameters['maxToProcess']:
return
status, messageLabels = _qualifyMessage(user, result) status, messageLabels = _qualifyMessage(user, result)
if not status: if not status:
return return
@@ -70872,6 +70945,7 @@ def printShowMessagesThreads(users, entityType):
if show_attachments or save_attachments or upload_attachments: if show_attachments or save_attachments or upload_attachments:
_showSaveAttachments(result['id'], result['payload'], attachmentNamePattern, j, jcount) _showSaveAttachments(result['id'], result['payload'], attachmentNamePattern, j, jcount)
Ind.Decrement() Ind.Decrement()
if checkMax:
parameters['messagesProcessed'] += 1 parameters['messagesProcessed'] += 1
def _getAttachments(messageId, payload, attachmentNamePattern, attachments): def _getAttachments(messageId, payload, attachmentNamePattern, attachments):
@@ -70894,7 +70968,9 @@ def printShowMessagesThreads(users, entityType):
else: else:
_getAttachments(messageId, part, attachmentNamePattern, attachments) _getAttachments(messageId, part, attachmentNamePattern, attachments)
def _printMessage(user, result): def _printMessage(user, result, checkMax=True):
if checkMax and parameters['maxToProcess'] and parameters['messagesProcessed'] == parameters['maxToProcess']:
return
status, messageLabels = _qualifyMessage(user, result) status, messageLabels = _qualifyMessage(user, result)
if not status: if not status:
return return
@@ -70948,6 +71024,7 @@ def printShowMessagesThreads(users, entityType):
row[f'Attachments{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{i}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}size'] = attachment[2] row[f'Attachments{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{i}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}size'] = attachment[2]
row[f'Attachments{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{i}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}charset'] = attachment[3] row[f'Attachments{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{i}{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}charset'] = attachment[3]
csvPF.WriteRowTitles(row) csvPF.WriteRowTitles(row)
if checkMax:
parameters['messagesProcessed'] += 1 parameters['messagesProcessed'] += 1
def _countMessageLabels(user, result): def _countMessageLabels(user, result):
@@ -70978,6 +71055,8 @@ def printShowMessagesThreads(users, entityType):
messageThreadCounts['size'] += result['sizeEstimate'] messageThreadCounts['size'] += result['sizeEstimate']
def _showThread(user, result, j, jcount): def _showThread(user, result, j, jcount):
if parameters['maxToProcess'] and parameters['messagesProcessed'] == parameters['maxToProcess']:
return
if senderMatchPattern: if senderMatchPattern:
for message in result['messages']: for message in result['messages']:
if _checkSenderMatch(message): if _checkSenderMatch(message):
@@ -70993,19 +71072,29 @@ def printShowMessagesThreads(users, entityType):
k = 0 k = 0
for message in result['messages']: for message in result['messages']:
k += 1 k += 1
_showMessage(user, message, k, kcount) _showMessage(user, message, k, kcount, False)
if k == parameters['maxMessagesPerThread']:
break
Ind.Decrement() Ind.Decrement()
parameters['messagesProcessed'] += 1
def _printThread(user, result): def _printThread(user, result):
if parameters['maxToProcess'] and parameters['messagesProcessed'] == parameters['maxToProcess']:
return
if senderMatchPattern: if senderMatchPattern:
for message in result['messages']: for message in result['messages']:
if _checkSenderMatch(message): if _checkSenderMatch(message):
break break
else: else:
return return
k = 0
for message in result['messages']: for message in result['messages']:
_printMessage(user, message) k += 1
_printMessage(user, message, False)
if k == parameters['maxMessagesPerThread']:
break
messageThreadCounts['threads'] += 1 messageThreadCounts['threads'] += 1
parameters['messagesProcessed'] += 1
def _countThreadLabels(user, result): def _countThreadLabels(user, result):
for message in result['messages']: for message in result['messages']:
@@ -71020,8 +71109,12 @@ def printShowMessagesThreads(users, entityType):
else: else:
return return
else: else:
k = 0
for message in result['messages']: for message in result['messages']:
k += 1
messageThreadCounts['size'] += message['sizeEstimate'] messageThreadCounts['size'] += message['sizeEstimate']
if k == parameters['maxMessagesPerThread']:
break
messageThreadCounts['threads'] += 1 messageThreadCounts['threads'] += 1
_GMAIL_ERROR_REASON_TO_MESSAGE_MAP = {GAPI.NOT_FOUND: Msg.DOES_NOT_EXIST, GAPI.INVALID_MESSAGE_ID: Msg.INVALID_MESSAGE_ID} _GMAIL_ERROR_REASON_TO_MESSAGE_MAP = {GAPI.NOT_FOUND: Msg.DOES_NOT_EXIST, GAPI.INVALID_MESSAGE_ID: Msg.INVALID_MESSAGE_ID}
@@ -74603,14 +74696,14 @@ def createNotesACLs(users):
request['parent'] = name request['parent'] = name
try: try:
permissions = callGAPI(keep.notes().permissions(), 'batchCreate', permissions = callGAPI(keep.notes().permissions(), 'batchCreate',
throwReasons=GAPI.KEEP_THROW_REASONS, throwReasons=GAPI.KEEP_THROW_REASONS+[GAPI.FAILED_PRECONDITION],
parent=name, body=rbody) parent=name, body=rbody)
entityNumItemsActionPerformed(entityKVList, kcount, Ent.NOTE_ACL, j, jcount) entityNumItemsActionPerformed(entityKVList, kcount, Ent.NOTE_ACL, j, jcount)
if showDetails: if showDetails:
Ind.Increment() Ind.Increment()
_showNotePermissions(permissions['permissions']) _showNotePermissions(permissions['permissions'])
Ind.Decrement() Ind.Decrement()
except (GAPI.badRequest, GAPI.invalidArgument, GAPI.notFound) as e: except (GAPI.badRequest, GAPI.invalidArgument, GAPI.notFound, GAPI.failedPrecondition) as e:
entityActionFailedWarning(entityKVList, str(e), i, count) entityActionFailedWarning(entityKVList, str(e), i, count)
except GAPI.authError: except GAPI.authError:
userKeepServiceNotEnabledWarning(user, i, count) userKeepServiceNotEnabledWarning(user, i, count)

View File

@@ -924,6 +924,7 @@ class GamCLArgs():
OB_EXPORT_ITEM = 'ExportItem' OB_EXPORT_ITEM = 'ExportItem'
OB_FIELD_NAME = 'FieldName' OB_FIELD_NAME = 'FieldName'
OB_FIELD_NAME_LIST = "FieldNameList" OB_FIELD_NAME_LIST = "FieldNameList"
OB_FIELDS = 'Fields'
OB_FILE_NAME = 'FileName' OB_FILE_NAME = 'FileName'
OB_FILE_NAME_FIELD_NAME = OB_FILE_NAME+'(:'+OB_FIELD_NAME+')+' OB_FILE_NAME_FIELD_NAME = OB_FILE_NAME+'(:'+OB_FIELD_NAME+')+'
OB_FILE_NAME_OR_URL = 'FileName|URL' OB_FILE_NAME_OR_URL = 'FileName|URL'

View File

@@ -23,8 +23,10 @@
# The following GM_XXX constants are arbitrary but must be unique # The following GM_XXX constants are arbitrary but must be unique
# Most errors print a message and bail out with a return code # Most errors print a message and bail out with a return code
# Some commands want to set a non-zero return code but not bail # Some commands want to set a non-zero return code but not bail
# GAM admin user # GAM admin user from oauth2.txt or oauth2service.json
ADMIN = 'admin' ADMIN = 'admn'
# Drive service for admin; used to look up Shared Drive Names
ADMIN_DRIVE = 'addr'
# Number/length of API call retries # Number/length of API call retries
API_CALLS_RETRY_DATA = 'rtry' API_CALLS_RETRY_DATA = 'rtry'
# GAM cache directory. If no_cache is True, this variable will be set to None # GAM cache directory. If no_cache is True, this variable will be set to None
@@ -215,6 +217,7 @@ REDIRECT_QUEUE_EOF = 'eof'
# #
Globals = { Globals = {
ADMIN: None, ADMIN: None,
ADMIN_DRIVE: None,
API_CALLS_RETRY_DATA: {}, API_CALLS_RETRY_DATA: {},
CACHE_DIR: None, CACHE_DIR: None,
CACHE_DISCOVERY_ONLY: True, CACHE_DISCOVERY_ONLY: True,

View File

@@ -11,4 +11,4 @@ lxml
passlib>=1.7.2 passlib>=1.7.2
pathvalidate pathvalidate
python-dateutil python-dateutil
yubikey-manager>=5.0 yubikey-manager[yubikey]>=5.0