mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-05 06:41:38 +00:00
Compare commits
307 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2199fb2828 | ||
|
|
b7d052a6b3 | ||
|
|
b333816dc8 | ||
|
|
90160da042 | ||
|
|
6f2ebf8d2d | ||
|
|
a65635365e | ||
|
|
0eee6979b0 | ||
|
|
ec796e9f84 | ||
|
|
aaed2a6d86 | ||
|
|
0ea7c500e1 | ||
|
|
d90c884cf2 | ||
|
|
93700c01a8 | ||
|
|
1df5662d4f | ||
|
|
338eeba944 | ||
|
|
9651e4abb1 | ||
|
|
ed1f3400ac | ||
|
|
e9d9353fbb | ||
|
|
00adf4ca46 | ||
|
|
870fc27c72 | ||
|
|
bd38b7479f | ||
|
|
a567599eae | ||
|
|
5e6f9353c2 | ||
|
|
7de1179b7e | ||
|
|
ea7c80c3a1 | ||
|
|
f252f757f1 | ||
|
|
b27c63d0d7 | ||
|
|
bcce1a4472 | ||
|
|
9d9655512d | ||
|
|
7af75f31e4 | ||
|
|
83f02c377f | ||
|
|
ce4f74bc61 | ||
|
|
66651d0eed | ||
|
|
ec0e143361 | ||
|
|
250e0188f7 | ||
|
|
3123e472fc | ||
|
|
c12f7f1123 | ||
|
|
7e706518c5 | ||
|
|
d8ca573983 | ||
|
|
2225625cd8 | ||
|
|
89f0f01fd2 | ||
|
|
a36282d114 | ||
|
|
a8c92b7f9a | ||
|
|
f505dac8f3 | ||
|
|
8e4730a3bd | ||
|
|
b094bb344b | ||
|
|
2685aa049d | ||
|
|
b738d57433 | ||
|
|
539b870754 | ||
|
|
abeb0998ea | ||
|
|
82faddd985 | ||
|
|
b8084c270e | ||
|
|
22c7da420c | ||
|
|
45a3c89b0b | ||
|
|
8fc9e6d1ee | ||
|
|
7f0b286d8e | ||
|
|
4f664df087 | ||
|
|
dff48e3146 | ||
|
|
0fefa19f80 | ||
|
|
88e07ddbaa | ||
|
|
44a3ef0d70 | ||
|
|
5e793f171f | ||
|
|
e9bc63bee8 | ||
|
|
5636876e42 | ||
|
|
f2f7f549b0 | ||
|
|
1fc6e4f781 | ||
|
|
d641458fb4 | ||
|
|
517d44fa3c | ||
|
|
80ee0bf9a8 | ||
|
|
0934b70414 | ||
|
|
f74168e2c7 | ||
|
|
bf4a6e6cde | ||
|
|
0e09675779 | ||
|
|
40e92ca3d2 | ||
|
|
e776919bfd | ||
|
|
84bfeffe46 | ||
|
|
1360abbecb | ||
|
|
2a13accfe4 | ||
|
|
e26dac3993 | ||
|
|
1b7a43e82b | ||
|
|
141aca9e25 | ||
|
|
4f99eb6f07 | ||
|
|
81075bb000 | ||
|
|
33057faaab | ||
|
|
28b831c6a2 | ||
|
|
ef4d3d2659 | ||
|
|
09c0c18fce | ||
|
|
ff80150216 | ||
|
|
a8203baa50 | ||
|
|
aa33dc83d4 | ||
|
|
4155e2bb64 | ||
|
|
9660cafa99 | ||
|
|
c55b9cfe96 | ||
|
|
78ea96767e | ||
|
|
d11d7a8ffc | ||
|
|
bec789d2fb | ||
|
|
0cda3fca31 | ||
|
|
4f8980184f | ||
|
|
ffa096d988 | ||
|
|
6e1b1ed9d5 | ||
|
|
48526b815e | ||
|
|
1ded893e7b | ||
|
|
c61bd01c0f | ||
|
|
f0adcc90c7 | ||
|
|
2abb13bb4c | ||
|
|
8f69c4c820 | ||
|
|
5652c52d96 | ||
|
|
ff29cc192e | ||
|
|
7428d0e734 | ||
|
|
caad9e999c | ||
|
|
24cb225381 | ||
|
|
2e3195c5ee | ||
|
|
bfa039a612 | ||
|
|
49f8988912 | ||
|
|
7e214dbe3b | ||
|
|
42ad12d8d8 | ||
|
|
842e6ef788 | ||
|
|
56b87039c2 | ||
|
|
1767a0889d | ||
|
|
3036366de5 | ||
|
|
a8f1031e0f | ||
|
|
054107c3b9 | ||
|
|
c478b22ab9 | ||
|
|
2f712499ea | ||
|
|
39b9622cdb | ||
|
|
59a3a68357 | ||
|
|
d920dbd79e | ||
|
|
49dd390c6b | ||
|
|
d20b4bc334 | ||
|
|
a973807e3e | ||
|
|
431b5f4f30 | ||
|
|
636799e567 | ||
|
|
d003e3fa1b | ||
|
|
07edbe6619 | ||
|
|
dc4a5a05fe | ||
|
|
a8c86eb53d | ||
|
|
8ef9a62dc9 | ||
|
|
29c9ed4135 | ||
|
|
0426a4ca0d | ||
|
|
af7bbe3cca | ||
|
|
3c55153752 | ||
|
|
a82ff4bb4e | ||
|
|
73759e9611 | ||
|
|
71d6d08f5a | ||
|
|
2f2be201a7 | ||
|
|
49aaca7172 | ||
|
|
f29c64984b | ||
|
|
a50329edf3 | ||
|
|
15d163abf4 | ||
|
|
852a116c9d | ||
|
|
e9c18d0c01 | ||
|
|
5e6d4ecb1c | ||
|
|
97635311db | ||
|
|
3c7ddfd236 | ||
|
|
e0aa0eb4a3 | ||
|
|
8eba9d252f | ||
|
|
5966680a31 | ||
|
|
eb1563b1e7 | ||
|
|
84f52668b7 | ||
|
|
cc60095344 | ||
|
|
5a1f237b30 | ||
|
|
934a671344 | ||
|
|
b81ea8e8c7 | ||
|
|
817920940e | ||
|
|
e0f7ebbcba | ||
|
|
8ce18960fe | ||
|
|
6a879927a7 | ||
|
|
9c22114aa5 | ||
|
|
add9bef046 | ||
|
|
64b6cfea93 | ||
|
|
c8e76d5727 | ||
|
|
4fda0b6aaa | ||
|
|
4634237879 | ||
|
|
5cc91fef53 | ||
|
|
4a3c408ec5 | ||
|
|
23f0c55053 | ||
|
|
e1cf21328a | ||
|
|
7ce5f982b3 | ||
|
|
8fb9439eff | ||
|
|
700e348dbd | ||
|
|
91a86a8663 | ||
|
|
293270c03d | ||
|
|
af13113161 | ||
|
|
59b9dca5ec | ||
|
|
bc5d4e8efb | ||
|
|
91111a62f6 | ||
|
|
5525324620 | ||
|
|
29bd632e45 | ||
|
|
369e3c7269 | ||
|
|
383fed7b3d | ||
|
|
849dd8d436 | ||
|
|
6340a35cf2 | ||
|
|
1b2ea1d4bd | ||
|
|
9087872bc2 | ||
|
|
d914e06f42 | ||
|
|
e12af0c870 | ||
|
|
b0b9e2a7de | ||
|
|
5b88d8b9ca | ||
|
|
b7010b099f | ||
|
|
5484d39d90 | ||
|
|
181a2f8949 | ||
|
|
cee0f97850 | ||
|
|
fd91388b7a | ||
|
|
beae08a99d | ||
|
|
aef614aeae | ||
|
|
c87bc39ad8 | ||
|
|
d0b78f8e81 | ||
|
|
f126cc1d6c | ||
|
|
2d8c3427c4 | ||
|
|
c2acd926af | ||
|
|
b904d77497 | ||
|
|
c093b92c0b | ||
|
|
734b8bfd40 | ||
|
|
e337d1f116 | ||
|
|
8434ac1e2f | ||
|
|
349cdbf582 | ||
|
|
693aeae9e5 | ||
|
|
e4ea8e156d | ||
|
|
ca2b7dd674 | ||
|
|
8a744aa7fc | ||
|
|
7f2beb4d80 | ||
|
|
103c421b31 | ||
|
|
0f4238e9a7 | ||
|
|
c9508d2dac | ||
|
|
5d293b4318 | ||
|
|
3f4b814c0b | ||
|
|
aca71d8db1 | ||
|
|
87dbe3c945 | ||
|
|
c254fc946f | ||
|
|
5a4718eae8 | ||
|
|
935c52f291 | ||
|
|
04fe93d3b8 | ||
|
|
22f279e309 | ||
|
|
a0cff87e5f | ||
|
|
943d327975 | ||
|
|
6c4aced95e | ||
|
|
ad80fd2a91 | ||
|
|
43d50734a4 | ||
|
|
52d6057365 | ||
|
|
8dd5a5dd8a | ||
|
|
4799b33e0e | ||
|
|
d25ae7de81 | ||
|
|
83cbbbf0b7 | ||
|
|
4794578688 | ||
|
|
446da392d9 | ||
|
|
07cbd4cdbb | ||
|
|
f17cbdc111 | ||
|
|
28c5d277a0 | ||
|
|
939840b702 | ||
|
|
78485ae4e9 | ||
|
|
cc47a93872 | ||
|
|
af122eeb5c | ||
|
|
ec118968ed | ||
|
|
2de416fe81 | ||
|
|
d10a3b91e3 | ||
|
|
cdadf68f30 | ||
|
|
900a123141 | ||
|
|
4b2f9488ce | ||
|
|
02d7f45988 | ||
|
|
cc34dbb88e | ||
|
|
5e9d99083c | ||
|
|
295bf74a1b | ||
|
|
7928437dc6 | ||
|
|
83283b7b6b | ||
|
|
09a289b4c4 | ||
|
|
6b940b9d01 | ||
|
|
cb492e0183 | ||
|
|
92799d57ae | ||
|
|
066100f218 | ||
|
|
cd4dd44004 | ||
|
|
40a2fdb7fd | ||
|
|
b60cf11668 | ||
|
|
0fa617c580 | ||
|
|
1cfa08612e | ||
|
|
2cebef9d4b | ||
|
|
c75313cdf4 | ||
|
|
ae1eaac037 | ||
|
|
01465a898a | ||
|
|
d6a7917ffd | ||
|
|
d9946088ab | ||
|
|
a2ad6a1037 | ||
|
|
34e240b40a | ||
|
|
ca1f33ade6 | ||
|
|
d8bddb1c21 | ||
|
|
64bab14483 | ||
|
|
2d1830f4fc | ||
|
|
ab00f2bd42 | ||
|
|
6d9505a4c0 | ||
|
|
40e3cb8ce5 | ||
|
|
4783ec6696 | ||
|
|
3bd746fe91 | ||
|
|
a91a82eecc | ||
|
|
a9564583cb | ||
|
|
f988c8879e | ||
|
|
825cad81a2 | ||
|
|
b9c0ea065a | ||
|
|
50ef633573 | ||
|
|
10202df7d7 | ||
|
|
a7815b41db | ||
|
|
cf467eb868 | ||
|
|
a3be19154f | ||
|
|
a7c19c689c | ||
|
|
1c78e3aac0 | ||
|
|
b9cc3d77b3 | ||
|
|
1feb81adf3 | ||
|
|
fd937758e6 | ||
|
|
fcc3d674c2 | ||
|
|
ce74264a01 |
14
.github/ISSUE_TEMPLATE/aa-question.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/aa-question.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Question about using GAM
|
||||
about: Help with using GAM or running it for the first time
|
||||
title: Please use the GAM discussion group
|
||||
labels: invalid
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
If you need help with GAM, please do not file an issue here, it will be closed and ignored.
|
||||
|
||||
Please post your question to the GAM discussion group where other admins are ready and willing to help:
|
||||
|
||||
https://groups.google.com/g/google-apps-manager
|
||||
23
.github/ISSUE_TEMPLATE/za-bug-report.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/za-bug-report.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: jay0lee
|
||||
|
||||
---
|
||||
|
||||
The issue tracker is for reporting product deficiencies. "How do I?" questions should be posted to the discussion forum at https://groups.google.com/group/google-apps-manager. When in doubt, start at the discussion forum and return here only when instructed to do so.
|
||||
|
||||
Please confirm the following:
|
||||
* I have upgraded to the latest GAM release from https://git.io/gamreleases and I still have this issue.
|
||||
* I am typing the command as described in the GAM Wiki at https://github.com/jay0lee/gam/wiki
|
||||
|
||||
Full steps to reproduce the issue:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
|
||||
Expected outcome (what are you trying to do?):
|
||||
|
||||
Actual outcome (what errors or bad behavior do you see instead?):
|
||||
20
.github/ISSUE_TEMPLATE/zz-feature-request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/zz-feature-request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for GAM
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: jay0lee
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
BIN
.github/actions/creds.tar.gpg
vendored
Normal file
BIN
.github/actions/creds.tar.gpg
vendored
Normal file
Binary file not shown.
16
.github/actions/decrypt.sh
vendored
Normal file
16
.github/actions/decrypt.sh
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
gpgfile="$1"
|
||||
echo "source file is ${gpgfile}"
|
||||
credsfile="$2"
|
||||
echo "target file is ${credsfile}"
|
||||
if [ -z ${PASSCODE+x} ]; then
|
||||
echo "PASSCODE is unset";
|
||||
else
|
||||
echo "PASSCODE is set";
|
||||
fi
|
||||
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="${PASSCODE}" \
|
||||
--output "${credsfile}" "${gpgfile}"
|
||||
|
||||
tar xf "${credsfile}" --directory "${gampath}"
|
||||
@@ -1,3 +1,6 @@
|
||||
echo "RUNNING: apt update..."
|
||||
sudo apt-get -qq --yes update > /dev/null
|
||||
sudo apt-get -qq --yes install swig libpcsclite-dev
|
||||
if [[ "$TRAVIS_JOB_NAME" == *"Testing" ]]; then
|
||||
export python="python"
|
||||
export pip="pip"
|
||||
@@ -5,7 +8,7 @@ if [[ "$TRAVIS_JOB_NAME" == *"Testing" ]]; then
|
||||
echo "running tests with this version"
|
||||
else
|
||||
export whereibelong=$(pwd)
|
||||
echo "We are running on Ubuntu $TRAVIS_DIST $PLATFORM"
|
||||
echo "We are running on $ImageOS $ImageVersion"
|
||||
export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
|
||||
cpucount=$(nproc --all)
|
||||
echo "This device has $cpucount CPUs for compiling..."
|
||||
@@ -32,8 +35,6 @@ else
|
||||
rm -rf python
|
||||
mkdir ssl
|
||||
mkdir python
|
||||
echo "RUNNING: apt update..."
|
||||
sudo apt-get -qq --yes update > /dev/null
|
||||
echo "RUNNING: apt upgrade..."
|
||||
sudo apt-mark hold openssh-server
|
||||
sudo apt-get --yes upgrade
|
||||
@@ -93,7 +94,7 @@ else
|
||||
python=~/python/bin/python3
|
||||
pip=~/python/bin/pip3
|
||||
|
||||
if ([ "${TRAVIS_DIST}" == "trusty" ] || [ "${TRAVIS_DIST}" == "xenial" ]) && [ "${PLATFORM}" == "x86_64" ]; then
|
||||
if ([ "${ImageOS}" == "ubuntu16" ]) && [ "${HOSTTYPE}" == "x86_64" ]; then
|
||||
echo "Installing deps for StaticX..."
|
||||
if [ ! -d patchelf-$PATCHELF_VERSION ]; then
|
||||
echo "Downloading PatchELF $PATCHELF_VERSION"
|
||||
@@ -108,11 +109,5 @@ else
|
||||
$pip install staticx
|
||||
fi
|
||||
|
||||
$pip install --upgrade git+git://github.com/pyinstaller/pyinstaller.git@$PYINSTALLER_COMMIT
|
||||
|
||||
cd $whereibelong
|
||||
fi
|
||||
|
||||
echo "Upgrading pip packages..."
|
||||
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
|
||||
$pip install --upgrade -r src/requirements.txt
|
||||
32
.github/actions/linux-install.sh
vendored
Executable file
32
.github/actions/linux-install.sh
vendored
Executable file
@@ -0,0 +1,32 @@
|
||||
export gampath="dist/gam"
|
||||
rm -rf $gampath
|
||||
mkdir -p $gampath
|
||||
export gampath=$(readlink -e $gampath)
|
||||
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath $gampath gam.spec
|
||||
export gam="${gampath}/gam"
|
||||
export GAMVERSION=`$gam version simple`
|
||||
cp LICENSE $gampath
|
||||
cp GamCommands.txt $gampath
|
||||
this_glibc_ver=$(ldd --version | awk '/ldd/{print $NF}')
|
||||
GAM_ARCHIVE="gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-glibc${this_glibc_ver}.tar.xz"
|
||||
rm $gampath/lastupdatecheck.txt
|
||||
# tar will cd to dist and tar up gam/
|
||||
tar -C dist/ --create --file $GAM_ARCHIVE --xz gam
|
||||
echo "PyInstaller GAM info:"
|
||||
du -h $gam
|
||||
time $gam version extended
|
||||
if ([ "${ImageOS}" == "ubuntu16" ]) && [ "${HOSTTYPE}" == "x86_64" ]; then
|
||||
GAM_LEGACY_ARCHIVE=gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-legacy.tar.xz
|
||||
$python -OO -m staticx -l /lib/x86_64-linux-gnu/libresolv.so.2 -l /lib/x86_64-linux-gnu/libnss_dns.so.2 $gam $gam-staticx
|
||||
strip $gam-staticx
|
||||
rm $gampath/gam
|
||||
mv $gam-staticx $gam
|
||||
chmod 755 $gam
|
||||
rm $gampath/lastupdatecheck.txt
|
||||
tar -C dist/ --create --file $GAM_LEGACY_ARCHIVE --xz gam
|
||||
echo "Legacy StaticX GAM info:"
|
||||
du -h $gam
|
||||
time $gam version extended
|
||||
fi
|
||||
echo "GAM packages:"
|
||||
ls -l gam-*.tar.xz
|
||||
136
.github/actions/macos-before-install.sh
vendored
Executable file
136
.github/actions/macos-before-install.sh
vendored
Executable file
@@ -0,0 +1,136 @@
|
||||
mypath=$HOME
|
||||
whereibelong=$(pwd)
|
||||
cpucount=$(sysctl -n hw.ncpu)
|
||||
echo "This device has $cpucount CPUs for compiling..."
|
||||
|
||||
#echo "Brew installing xz..."
|
||||
#brew install xz > /dev/null
|
||||
|
||||
#brew upgrade
|
||||
|
||||
brew install coreutils
|
||||
brew install bash
|
||||
|
||||
# prefer standard GNU tools like date over MacOS defaults
|
||||
export PATH="/usr/local/opt/coreutils/libexec/gnubin:$(brew --prefix)/opt/gnu-tar/libexec/gnubin:$PATH"
|
||||
|
||||
date --version
|
||||
gdate --version
|
||||
bash --version
|
||||
|
||||
cd ~
|
||||
|
||||
# Use official Python.org version of Python which is backwards compatible
|
||||
# with older MacOS versions
|
||||
if [ "$PLATFORM" == "x86_64" ]; then
|
||||
export pyfile=python-$BUILD_PYTHON_VERSION-macosx10.9.pkg
|
||||
else
|
||||
export pyfile=python-$BUILD_PYTHON_VERSION-macos11.0.pkg
|
||||
fi
|
||||
|
||||
wget https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/$pyfile
|
||||
echo "installing Python $BUILD_PYTHON_VERSION..."
|
||||
sudo installer -pkg ./$pyfile -target /
|
||||
|
||||
# This fixes https://github.com/pyinstaller/pyinstaller/issues/5062
|
||||
codesign --remove-signature /Library/Frameworks/Python.framework/Versions/3.9/Python
|
||||
|
||||
#if [ ! -f python-$MIN_PYTHON_VERSION-macosx10.9.pkg ]; then
|
||||
# wget --quiet https://www.python.org/ftp/python/$MIN_PYTHON_VERSION/python-$MIN_PYTHON_VERSION-macosx10.9.pkg
|
||||
#fi
|
||||
#sudo installer -pkg python-$MIN_PYTHON_VERSION-macosx10.9.pkg -target /
|
||||
|
||||
#brew install openssl@1.1
|
||||
#brew upgrade python
|
||||
|
||||
#export python=python3
|
||||
#export pip=pip3
|
||||
|
||||
#echo "Python location:"
|
||||
#which $python
|
||||
|
||||
cd ~
|
||||
|
||||
#export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
|
||||
#export openssl=~/ssl/bin/openssl
|
||||
#export python=~/python/bin/python3
|
||||
#export pip=~/python/bin/pip3
|
||||
|
||||
export python=/usr/local/bin/python3
|
||||
export pip=/usr/local/bin/pip3
|
||||
SSLVER=$($openssl version)
|
||||
SSLRESULT=$?
|
||||
PYVER=$($python -V)
|
||||
PYRESULT=$?
|
||||
|
||||
brew install swig
|
||||
$pip install pyscard
|
||||
|
||||
#wget --quiet https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/python-$BUILD_PYTHON_VERSION-macosx10.9.pkg
|
||||
|
||||
#if [ $SSLRESULT -ne 0 ] || [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]] || [ $PYRESULT -ne 0 ] || [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION"* ]]; then
|
||||
# echo "SSL Result: $SSLRESULT - SSL Ver: $SSLVER - Py Result: $PYRESULT - Py Ver: $PYVER"
|
||||
# if [ $SSLRESULT -ne 0 ]; then
|
||||
# echo "sslresult -ne 0"
|
||||
# fi
|
||||
# if [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]]; then
|
||||
# echo "sslver not equal to..."
|
||||
# fi
|
||||
# if [ $PYRESULT -ne 0 ]; then
|
||||
# echo "pyresult -ne 0"
|
||||
# fi
|
||||
# if [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION" ]]; then
|
||||
# echo "pyver not equal to..."
|
||||
# fi
|
||||
|
||||
# Start clean
|
||||
# rm -rf python
|
||||
# rm -rf ssl
|
||||
# mkdir python
|
||||
# mkdir ssl
|
||||
|
||||
# Compile latest OpenSSL
|
||||
# wget --quiet https://www.openssl.org/source/openssl-$BUILD_OPENSSL_VERSION.tar.gz
|
||||
# echo "Extracting OpenSSL..."
|
||||
# tar xf openssl-$BUILD_OPENSSL_VERSION.tar.gz
|
||||
# cd openssl-$BUILD_OPENSSL_VERSION
|
||||
# echo "Compiling OpenSSL $BUILD_OPENSSL_VERSION..."
|
||||
# ./config shared --prefix=$HOME/ssl
|
||||
# echo "Running make for OpenSSL..."
|
||||
# make -j$cpucount -s
|
||||
# echo "Running make install for OpenSSL..."
|
||||
# make install > /dev/null
|
||||
# cd ~
|
||||
|
||||
# Compile latest Python
|
||||
# echo "Downloading Python $BUILD_PYTHON_VERSION..."
|
||||
# curl -O https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
# echo "Extracting Python..."
|
||||
# tar xf Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
# cd Python-$BUILD_PYTHON_VERSION
|
||||
# echo "Compiling Python $BUILD_PYTHON_VERSION..."
|
||||
# safe_flags="--with-openssl=$HOME/ssl --enable-shared --prefix=$HOME/python --with-ensurepip=upgrade"
|
||||
# unsafe_flags="--enable-optimizations --with-lto"
|
||||
# if [ ! -e Makefile ]; then
|
||||
# echo "running configure with safe and unsafe"
|
||||
# ./configure $safe_flags $unsafe_flags > /dev/null
|
||||
# fi
|
||||
# make -j$cpucount PROFILE_TASK="-m test.regrtest --pgo -j$(( $cpucount * 2 ))" -s
|
||||
# RESULT=$?
|
||||
# echo "First make exited with $RESULT"
|
||||
# if [ $RESULT != 0 ]; then
|
||||
# echo "Trying Python compile again without unsafe flags..."
|
||||
# make clean
|
||||
# ./configure $safe_flags > /dev/null
|
||||
# make -j$cpucount -s
|
||||
# echo "Sticking with safe Python for now..."
|
||||
# fi
|
||||
# echo "Installing Python..."
|
||||
# make install > /dev/null
|
||||
# cd ~
|
||||
#fi
|
||||
|
||||
$python -V
|
||||
|
||||
cd $whereibelong
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
cd src
|
||||
echo "MacOS Version Info According to Python:"
|
||||
python -c "import platform; print(platform.mac_ver())"
|
||||
echo "Xcode versionn:"
|
||||
xcodebuild -version
|
||||
export gampath=dist/gam
|
||||
rm -rf $gampath
|
||||
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath $gampath gam.spec
|
||||
export gam="$gampath/gam"
|
||||
if [ "$PLATFORM" == "x86_64" ]; then
|
||||
export specfile="gam.spec"
|
||||
else
|
||||
export specfile="gam-universal2.spec"
|
||||
fi
|
||||
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath "${gampath}" "${specfile}"
|
||||
export gam="${gampath}/gam"
|
||||
$gam version extended
|
||||
export GAMVERSION=`$gam version simple`
|
||||
cp LICENSE $gampath
|
||||
cp GamCommands.txt $gampath
|
||||
cp LICENSE "${gampath}"
|
||||
cp GamCommands.txt "${gampath}"
|
||||
MACOSVERSION=$(defaults read loginwindow SystemVersionStampAsString)
|
||||
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM-MacOS$MACOSVERSION.tar.xz
|
||||
rm $gampath/lastupdatecheck.txt
|
||||
GAM_ARCHIVE="gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-MacOS${MACOSVERSION}.tar.xz"
|
||||
rm "${gampath}/lastupdatecheck.txt"
|
||||
# tar will cd to dist/ and tar up gam/
|
||||
tar -C dist/ --create --file $GAM_ARCHIVE --xz gam
|
||||
57
.github/actions/windows-before-install.sh
vendored
Executable file
57
.github/actions/windows-before-install.sh
vendored
Executable file
@@ -0,0 +1,57 @@
|
||||
if [[ "$PLATFORM" == "x86_64" ]]; then
|
||||
export BITS="64"
|
||||
export PYTHONFILE_BITS="-amd64"
|
||||
export OPENSSL_BITS="-x64"
|
||||
elif [[ "$PLATFORM" == "x86" ]]; then
|
||||
export BITS="32"
|
||||
export PYTHONFILE_BITS=""
|
||||
export OPENSSL_BITS=""
|
||||
export CHOCOPTIONS="--forcex86"
|
||||
fi
|
||||
echo "This is a ${BITS}-bit build for ${PLATFORM}"
|
||||
|
||||
export mypath=$(pwd)
|
||||
cd ~
|
||||
|
||||
export python="python"
|
||||
export pip="pip"
|
||||
|
||||
# pyscard needs swig, keep these two together
|
||||
choco install $CHOCOPTIONS swig
|
||||
$pip install pyscard
|
||||
|
||||
# Python
|
||||
#echo "Installing Python..."
|
||||
#export python_file=python-${BUILD_PYTHON_VERSION}${PYTHONFILE_BITS}.exe
|
||||
#if [ ! -e $python_file ]; then
|
||||
# echo "Downloading $python_file..."
|
||||
# curl -O https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/$python_file
|
||||
#fi
|
||||
#until ./${python_file} /quiet InstallAllUsers=1 TargetDir=c:\\python; do echo "trying python again..."; done
|
||||
#export python=/c/python/python.exe
|
||||
#export pip=/c/python/scripts/pip.exe
|
||||
#until [ -f $python ]; do sleep 1; done
|
||||
#export PATH=$PATH:/c/python/scripts
|
||||
|
||||
# OpenSSL
|
||||
#echo "Installing OpenSSL..."
|
||||
#export exefile=Win${BITS}OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
|
||||
#if [ ! -e $exefile ]; then
|
||||
# echo "Downloading $exefile..."
|
||||
# curl -O https://slproweb.com/download/$exefile
|
||||
#fi
|
||||
#until ./${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl; do echo "trying openssl again..."; done
|
||||
#until cp -v /c/ssl/libcrypto-1_1${OPENSSL_BITS}.dll /c/python/DLLs/; do echo "trying libcrypto copy again..."; sleep 3; done
|
||||
#until cp -v /c/ssl/libssl-1_1${OPENSSL_BITS}.dll /c/python/DLLs/; do echo "trying libssl copy again..."; done
|
||||
#if [[ "$PLATFORM" == "x86_64" ]]; then
|
||||
# cp -v /c/python/DLLs/libssl-1_1-x64.dll /c/python/DLLs/libssl-1_1.dll
|
||||
# cp -v /c/python/DLLs/libcrypto-1_1-x64.dll /c/python/DLLs/libcrypto-1_1.dll
|
||||
#fi
|
||||
|
||||
cd $mypath
|
||||
|
||||
echo "PATH: $PATH"
|
||||
cd ..
|
||||
$python setup.py install
|
||||
echo "cd to $mypath"
|
||||
cd $mypath
|
||||
@@ -1,4 +1,8 @@
|
||||
cd src
|
||||
if [[ "$PLATFORM" == "x86_64" ]]; then
|
||||
export WIX_BITS="x64"
|
||||
elif [[ "$PLATFORM" == "x86" ]]; then
|
||||
export WIX_BITS="x86"
|
||||
fi
|
||||
echo "compiling GAM with pyinstaller..."
|
||||
export gampath="dist/gam"
|
||||
rm -rf $gampath
|
||||
@@ -8,7 +12,7 @@ pyinstaller --clean --noupx -F --distpath $gampath gam.spec
|
||||
export gam="${gampath}/gam"
|
||||
echo "running compiled GAM..."
|
||||
$gam version
|
||||
export GAMVERSION=`$gam version simple`
|
||||
export GAMVERSION=$($gam version simple)
|
||||
rm $gampath/lastupdatecheck.txt
|
||||
cp LICENSE $gampath
|
||||
cp GamCommands.txt $gampath
|
||||
344
.github/workflows/build.yml
vendored
Normal file
344
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
name: Build and test GAM
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '37 22 * * *'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: src
|
||||
|
||||
env:
|
||||
BUILD_PYTHON_VERSION: "3.9.2"
|
||||
MIN_PYTHON_VERSION: "3.9.2"
|
||||
BUILD_OPENSSL_VERSION: "1.1.1j"
|
||||
MIN_OPENSSL_VERSION: "1.1.1i"
|
||||
PATCHELF_VERSION: "0.12"
|
||||
# PYINSTALLER_VERSION can be full commit hash or version like v4.20
|
||||
PYINSTALLER_VERSION: "227eac14955c02db21d4702429896d4b74beed5e"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-16.04
|
||||
jid: 1
|
||||
goal: "build"
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
- os: ubuntu-18.04
|
||||
jid: 2
|
||||
goal: "build"
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
- os: ubuntu-20.04
|
||||
jid: 3
|
||||
goal: "build"
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
# - os: [self-hosted, linux, ARM]
|
||||
# jid: 10
|
||||
# goal: "build"
|
||||
# gamos: "linux"
|
||||
# platform: "arm"
|
||||
# - os: [self-hosted, linux, ARM64]
|
||||
# jid: 11
|
||||
# goal: "build"
|
||||
# gamos: "linux"
|
||||
# platform: "arm64"
|
||||
- os: macos-10.15
|
||||
jid: 4
|
||||
goal: "build"
|
||||
gamos: "macos"
|
||||
platform: "x86_64"
|
||||
# - os: macos-11.0
|
||||
# jid: 12
|
||||
# goal: "build"
|
||||
# gamos: "macos"
|
||||
# platform: "universal2"
|
||||
- os: windows-2019
|
||||
jid: 5
|
||||
goal: "build"
|
||||
gamos: "windows"
|
||||
python: 3.9.2
|
||||
pyarch: "x64"
|
||||
platform: "x86_64"
|
||||
- os: windows-2019
|
||||
jid: 6
|
||||
goal: "build"
|
||||
gamos: "windows"
|
||||
platform: "x86"
|
||||
python: 3.9.2
|
||||
pyarch: "x86"
|
||||
- os: ubuntu-20.04
|
||||
goal: "test"
|
||||
python: "3.6"
|
||||
jid: 7
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
- os: ubuntu-20.04
|
||||
goal: "test"
|
||||
python: "3.7"
|
||||
jid: 8
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
- os: ubuntu-20.04
|
||||
goal: "test"
|
||||
python: "3.8"
|
||||
jid: 9
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache multiple paths
|
||||
uses: actions/cache@v2
|
||||
if: matrix.goal != 'test'
|
||||
with:
|
||||
path: |
|
||||
~/python
|
||||
~/ssl
|
||||
key: ${{ matrix.os }}-${{ matrix.jid }}-20210219
|
||||
|
||||
- name: Set env variables
|
||||
env:
|
||||
GAMOS: ${{ matrix.gamos }}
|
||||
GOAL: ${{ matrix.goal }}
|
||||
JID: ${{ matrix.jid }}
|
||||
PLATFORM: ${{ matrix.platform }}
|
||||
run: |
|
||||
echo "GAMOS=${GAMOS}" >> $GITHUB_ENV
|
||||
echo "GOAL=${GOAL}" >> $GITHUB_ENV
|
||||
echo "JID=${JID}" >> $GITHUB_ENV
|
||||
echo "PLATFORM=${PLATFORM}" >> $GITHUB_ENV
|
||||
uname -a
|
||||
|
||||
- name: Use pre-compiled Python for testing and Windows
|
||||
if: matrix.python != ''
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
architecture: ${{ matrix.pyarch }}
|
||||
|
||||
- name: Set env variables for pre-compiled Python
|
||||
if: matrix.goal == 'test'
|
||||
run: |
|
||||
export python=$(which python3)
|
||||
export pip=$(which pip3)
|
||||
export gam="${python} -m gam"
|
||||
export gampath="$(readlink -e .)"
|
||||
echo -e "python: $python\npip: $pip\ngam: $gam\ngampath: $gampath"
|
||||
echo "python=${python}" >> $GITHUB_ENV
|
||||
echo "pip=${pip}" >> $GITHUB_ENV
|
||||
echo "gam=${gam}" >> $GITHUB_ENV
|
||||
echo "gampath=${gampath}" >> $GITHUB_ENV
|
||||
echo "RUNNING: apt update..."
|
||||
sudo apt-get -qq --yes update > /dev/null
|
||||
sudo apt-get -qq --yes install swig libpcsclite-dev
|
||||
|
||||
- name: Build and install Python, OpenSSL and PyInstaller
|
||||
if: matrix.goal != 'test' && steps.cache-primes.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
set +e
|
||||
source ../.github/actions/${GAMOS}-before-install.sh
|
||||
echo "PATH=$PATH" >> $GITHUB_ENV # keep gnutools for MacOS
|
||||
echo "python=$python" >> $GITHUB_ENV
|
||||
echo "pip=$pip" >> $GITHUB_ENV
|
||||
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> $GITHUB_ENV
|
||||
echo -e "Python: $python\nPip: $pip\nLD_LIB...: $LD_LIBRARY_PATH"
|
||||
export url="https://codeload.github.com/pyinstaller/pyinstaller/tar.gz/${PYINSTALLER_VERSION}"
|
||||
echo "Downloading ${url}"
|
||||
curl -o pyinstaller.tar.gz --compressed "${url}"
|
||||
tar xf pyinstaller.tar.gz
|
||||
cd "pyinstaller-${PYINSTALLER_VERSION}/bootloader"
|
||||
if [ "${PLATFORM}" == "x86" ]; then
|
||||
BITS="32"
|
||||
else
|
||||
BITS="64"
|
||||
fi
|
||||
$python ./waf all --target-arch=${BITS}bit
|
||||
cd ..
|
||||
$python setup.py install
|
||||
#$pip install pyinstaller
|
||||
|
||||
- name: Install pip requirements
|
||||
if: matrix.os != 'self-hosted'
|
||||
run: |
|
||||
set +e
|
||||
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U --force-reinstall
|
||||
|
||||
$pip install --upgrade -r requirements.txt
|
||||
|
||||
- name: Build GAM with PyInstaller
|
||||
if: matrix.goal != 'test'
|
||||
run: |
|
||||
set +e
|
||||
source ../.github/actions/${GAMOS}-install.sh
|
||||
echo "gampath=$gampath" >> $GITHUB_ENV
|
||||
echo "gam=$gam" >> $GITHUB_ENV
|
||||
echo -e "GAM: ${gam}\nGAMPATH: ${gampath}\nGAMVERSION: ${GAMVERSION}"
|
||||
|
||||
- name: Basic Tests all jobs
|
||||
run: |
|
||||
echo -e "python: $python\npip: $pip\ngam: $gam\ngampath: $gampath\n"
|
||||
$python -m unittest discover --start-directory ./ --pattern "*_test.py" --buffer
|
||||
touch "${gampath}/nobrowser.txt"
|
||||
$gam version extended
|
||||
export GAMVERSION=$($gam version simple)
|
||||
echo "GAM Version ${GAMVERSION}"
|
||||
echo "GAMVERSION=${GAMVERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Basic Tests build jobs only
|
||||
if: matrix.goal != 'test'
|
||||
run: |
|
||||
export vline=$($gam version | grep "Python ")
|
||||
export python_line=($vline)
|
||||
export this_python=${python_line[1]}
|
||||
$python tools/a_atleast_b.py "${this_python}" "${MIN_PYTHON_VERSION}"
|
||||
export vline=$($gam version extended | grep "OpenSSL ")
|
||||
export openssl_line=($vline)
|
||||
export this_openssl="${openssl_line[1]}"
|
||||
$python tools/a_atleast_b.py "${this_openssl}" "${MIN_OPENSSL_VERSION}"
|
||||
|
||||
|
||||
- name: Live API tests push only
|
||||
if: github.event_name == 'push' || github.event_name == 'schedule'
|
||||
env: # Or as an environment variable
|
||||
PASSCODE: ${{ secrets.PASSCODE }}
|
||||
run: |
|
||||
source ../.github/actions/decrypt.sh ../.github/actions/creds.tar.gpg creds.tar
|
||||
export OAUTHFILE="oauth2.txt-gam-gha-${JID}"
|
||||
echo "OAUTHFILE=${OAUTHFILE}" >> $GITHUB_ENV
|
||||
export gam_user="gam-gha-${JID}@pdl.jaylee.us"
|
||||
echo "gam_user=${gam_user}" >> $GITHUB_ENV
|
||||
$gam oauth info
|
||||
$gam info domain
|
||||
$gam oauth refresh
|
||||
$gam info user
|
||||
export tstamp=$(date +%s%3N)
|
||||
export newbase=gha-test-$JID-$tstamp
|
||||
export newuser=$newbase@pdl.jaylee.us
|
||||
export newgroup=$newbase-group@pdl.jaylee.us
|
||||
export newalias=$newbase-alias@pdl.jaylee.us
|
||||
export newbuilding=$newbase-building
|
||||
export newresource=$newbase-resource
|
||||
export GAM_THREADS=5
|
||||
echo email > sample.csv;
|
||||
for i in {01..10}; do
|
||||
echo "${newbase}-bulkuser-$i" >> sample.csv;
|
||||
done
|
||||
$gam create user $newuser firstname GHA lastname $JID password random recoveryphone 12125121110 recoveryemail jay0lee@gmail.com gha.jid $JID
|
||||
$gam user $gam_user sendemail recipient $newuser subject "test message $newbase" message "GHA test message"
|
||||
$gam user $gam_user sendemail recipient exchange@pdl.jaylee.us subject "test ${tstamp}" message "test message"
|
||||
$gam create group $newgroup name "GHA $JID group" description "This is a description" isarchived true
|
||||
$gam user $newuser add license gsuitebusiness
|
||||
$gam update group $newgroup add owner $gam_user
|
||||
$gam update group $newgroup add member $newuser
|
||||
$gam csv sample.csv gam create user ~~email~~ firstname "GHA Bulk" lastname ~~email~~ gha.jid $JID
|
||||
$gam csv sample.csv gam update user ~~email~~ recoveryphone 12125121110 recoveryemail jay0lee@gmail.com password random
|
||||
$gam csv sample.csv gam update user ~~email~~ recoveryphone "" recoveryemail ""
|
||||
$gam csv sample.csv gam user ~email add license gsuitebusiness
|
||||
$gam csv sample.csv gam user $gam_user sendemail recipient ~~email~~@pdl.jaylee.us subject "test message $newbase" message "GHA test message"
|
||||
$gam csv sample.csv gam update group $newgroup add member ~email
|
||||
$gam info group $newgroup
|
||||
$gam user $gam_user check serviceaccount
|
||||
# confirm mailbox is provisoned before continuing
|
||||
$gam user $newuser waitformailbox
|
||||
$gam user $newuser imap on
|
||||
$gam user $newuser show imap
|
||||
$gam user $newuser show delegates
|
||||
#$gam user $newuser add contactdelegate "${newbase}-bulkuser-01"
|
||||
#$gam user $newuser print contactdelegates
|
||||
export biohazard=$(echo -e '\xe2\x98\xa3')
|
||||
$gam user $newuser label "$biohazard unicode biohazard $biohazard"
|
||||
$gam user $newuser show labels
|
||||
$gam user $newuser show labels > labels.txt
|
||||
$gam user $gam_user importemail subject "GHA import $newbase" message "This is a test import" labels IMPORTANT,UNREAD,INBOX,STARRED
|
||||
$gam user $gam_user insertemail subject "GHA insert $newbase" file gam.py labels INBOX,UNREAD # yep body is gam code
|
||||
$gam user $gam_user sendemail subject "GHA send $gam_user $newbase" file gam.py recipient admin@pdl.jaylee.us
|
||||
$gam user $gam_user draftemail subject "GHA draft $newbase" message "Draft message test"
|
||||
$gam csvfile sample.csv:email waitformailbox
|
||||
$gam user $newuser delegate to "${newbase}-bulkuser-01"
|
||||
$gam users "$gam_user $newbase-bulkuser-01 $newbase-bulkuser-02 $newbase-bulkuser-03" delete messages query in:anywhere maxtodelete 99999 doit
|
||||
$gam users "$newbase-bulkuser-04 $newbase-bulkuser-05 $newbase-bulkuser-06" trash messages query in:anywhere maxtotrash 99999 doit
|
||||
$gam users "$newbase-bulkuser-07 $newbase-bulkuser-08 $newbase-bulkuser-09" modify messages query in:anywhere maxtomodify 99999 addlabel IMPORTANT addlabel STARRED doit
|
||||
$gam user $newuser delete label --ALL_LABELS--
|
||||
$gam create feature name Whiteboard-$newbase
|
||||
$gam create feature name VC-$newbase
|
||||
$gam create building "My Building - $newbase" id $newbuilding floors 1,2,3,4,5,6,7,8,9,10,11,12,14,15 description "No 13th floor here..."
|
||||
$gam create resource $newresource "Resource Calendar $tstamp" capacity 25 features Whiteboard-$newbase,VC-$newbase building $newbuilding floor 15 type Room
|
||||
$gam info resource $newresource
|
||||
$gam user $newuser show filelist
|
||||
$gam calendar $gam_user printacl | $gam csv - gam calendar $gam_user delete id ~id # clear ACLs
|
||||
$gam calendar $gam_user update read domain
|
||||
$gam calendar $gam_user update freebusy default
|
||||
$gam calendar $gam_user add editor $newuser
|
||||
$gam calendar $gam_user showacl
|
||||
$gam calendar $gam_user printacl | $gam csv - gam calendar $gam_user delete id ~id
|
||||
$gam calendar $gam_user addevent summary "GHA test event" start $(date '+%FT%T.%N%:z' -d "now + 1 hour") end $(date '+%FT%T.%N%:z' -d "now + 2 hours") attendee $newgroup hangoutsmeet guestscanmodify true sendupdates all
|
||||
$gam calendar $gam_user printevents after -0d
|
||||
matterid=uid:$($gam create vaultmatter name "GHA matter $newbase" description "test matter" collaborators $newuser | head -1 | cut -d ' ' -f 3)
|
||||
$gam create vaulthold matter $matterid name "GHA hold $newbase" corpus mail accounts $newuser
|
||||
$gam print vaultmatters matterstate open
|
||||
$gam print vaultholds matter $matterid
|
||||
$gam print vaultcount matter $matterid corpus mail everyone todrive
|
||||
$gam create vaultexport matter $matterid name "GHA export $newbase" corpus mail accounts $newuser
|
||||
$gam print exports matter $matterid | $gam csv - gam info export $matterid id:~~id~~
|
||||
$gam csv sample.csv gam user ~email add calendar id:$newresource
|
||||
$gam delete resource $newresource
|
||||
$gam delete feature Whiteboard-$newbase
|
||||
$gam delete feature VC-$newbase
|
||||
$gam delete building $newbuilding
|
||||
$gam delete group $newgroup
|
||||
$gam create alias $newalias user $newuser
|
||||
$gam whatis $newuser
|
||||
$gam user $gam_user show tokens
|
||||
$gam print exports matter $matterid | $gam csv - gam download export $matterid id:~~id~~
|
||||
$gam delete hold "GHA hold $newbase" matter $matterid
|
||||
$gam update matter $matterid action close
|
||||
$gam update matter $matterid action delete
|
||||
$gam delete user $newuser
|
||||
$gam print users query "gha.jid=$JID" | $gam csv - gam delete user ~primaryEmail
|
||||
$gam print mobile
|
||||
$gam print devices
|
||||
$gam print browsers
|
||||
export sn="$JID$JID$JID$JID-$(openssl rand -base64 32 | sed 's/[^a-zA-Z0-9]//g')"
|
||||
$gam create device serialnumber $sn devicetype android
|
||||
$gam print cros allfields nolists
|
||||
$gam report usageparameters customer
|
||||
$gam report usage customer parameters gmail:num_emails_sent,accounts:num_1day_logins
|
||||
$gam report customer todrive
|
||||
$gam report users fields accounts:is_less_secure_apps_access_allowed,gmail:last_imap_time,gmail:last_pop_time filters "accounts:last_login_time>2019-01-01T00:00:00.000Z" todrive
|
||||
$gam report admin start -3d todrive
|
||||
$gam print devices nopersonaldevices nodeviceusers filter "serial:$JID$JID$JID$JID-" | $gam csv - gam delete device id ~name
|
||||
$gam print userinvitations
|
||||
$gam print userinvitations | $gam csv - gam create userinvitation ~name
|
||||
export CUSTOMER_ID="C01wfv983"
|
||||
export GA_DOMAIN="pdl.jaylee.us"
|
||||
touch $gampath/enabledasa.txt
|
||||
echo "printer model count:"
|
||||
$gam print printermodels | wc -l
|
||||
#$gam print printers
|
||||
#$gam create printer displayname "${newbase}" uri ipp://localhost:631 driverless description "made by $(date)"
|
||||
rm $gampath/enabledasa.txt
|
||||
|
||||
- name: Upload to Google Drive, build only.
|
||||
if: github.event_name == 'push' && matrix.goal != 'test'
|
||||
run: |
|
||||
ls gam-$GAMVERSION-*
|
||||
for gamfile in gam-$GAMVERSION-*; do
|
||||
echo "Uploading file ${gamfile} to Google Drive..."
|
||||
fileid=$($gam user $gam_user add drivefile localfile $gamfile drivefilename $GAMVERSION-${GITHUB_SHA:0:7}-$gamfile parentid 1N2zbO33qzUQFsGM49-m9AQC1ijzd_ru1 returnidonly)
|
||||
echo "file uploaded as ${fileid}, setting ACL..."
|
||||
$gam user $gam_user add drivefileacl $fileid anyone role reader withlink
|
||||
done
|
||||
253
.travis.yml
253
.travis.yml
@@ -1,253 +0,0 @@
|
||||
if: tag IS blank
|
||||
os: linux
|
||||
language: python
|
||||
dist: focal
|
||||
|
||||
env:
|
||||
global:
|
||||
- BUILD_PYTHON_VERSION=3.9.0
|
||||
- MIN_PYTHON_VERSION=3.9.0
|
||||
- BUILD_OPENSSL_VERSION=1.1.1h
|
||||
- MIN_OPENSSL_VERSION=1.1.1h
|
||||
- PATCHELF_VERSION=0.11
|
||||
- PYINSTALLER_COMMIT=7aa19839c171d898b5cf957739083c4bb901607e
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/pip
|
||||
- $HOME/python
|
||||
- $HOME/ssl
|
||||
|
||||
jobs:
|
||||
allow_failures:
|
||||
- python: nightly
|
||||
fast_finish: true
|
||||
include:
|
||||
- os: linux
|
||||
name: "Linux 64-bit Focal"
|
||||
dist: focal
|
||||
language: shell
|
||||
- os: linux
|
||||
name: "Linux 64-bit Bionic"
|
||||
dist: bionic
|
||||
language: shell
|
||||
- os: linux
|
||||
name: "Linux 64-bit Xenial"
|
||||
dist: xenial
|
||||
language: shell
|
||||
- os: linux
|
||||
name: "Linux ARM64 Focal"
|
||||
dist: focal
|
||||
language: shell
|
||||
arch: arm64
|
||||
filter_secrets: false
|
||||
- os: linux
|
||||
dist: bionic
|
||||
arch: arm64
|
||||
name: "Linux ARM64 Bionic"
|
||||
language: shell
|
||||
filter_secrets: false
|
||||
- os: linux
|
||||
dist: xenial
|
||||
arch: arm64
|
||||
name: "Linux ARM64 Xenial"
|
||||
language: shell
|
||||
filter_secrets: false
|
||||
- os: linux
|
||||
name: "Python 3.6 Source Testing"
|
||||
language: python
|
||||
python: 3.6
|
||||
- os: linux
|
||||
name: "Python 3.7 Source Testing"
|
||||
language: python
|
||||
python: 3.7
|
||||
- os: linux
|
||||
name: "Python 3.8 Source Testing"
|
||||
language: python
|
||||
python: 3.8
|
||||
- os: linux
|
||||
name: "Python 3.10 dev Source Testing"
|
||||
language: python
|
||||
python: 3.10-dev
|
||||
# - os: linux
|
||||
# name: "Python trunk nightly Source Testing"
|
||||
# language: python
|
||||
# python: nightly
|
||||
# - os: linux
|
||||
# name: "Python PyPi Source Testing"
|
||||
# language: python
|
||||
# python: pypy3
|
||||
- os: osx
|
||||
name: "MacOS 10.13"
|
||||
language: generic
|
||||
osx_image: xcode10.1
|
||||
- os: osx
|
||||
name: "MacOS 10.14"
|
||||
language: generic
|
||||
osx_image: xcode11.3
|
||||
- os: osx
|
||||
name: "MacOS 10.15"
|
||||
language: generic
|
||||
osx_image: xcode11.7
|
||||
- os: osx
|
||||
name: "MacOS 10.15 Universal Testing"
|
||||
language: generic
|
||||
osx_image: xcode12u
|
||||
- os: windows
|
||||
name: "Windows 64-bit"
|
||||
language: shell
|
||||
- os: windows
|
||||
name: "Windows 32-bit"
|
||||
language: shell
|
||||
|
||||
before_install:
|
||||
- if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
|
||||
export GAMOS="macos";
|
||||
else
|
||||
export GAMOS="${TRAVIS_OS_NAME}";
|
||||
fi
|
||||
- if [ "${TRAVIS_JOB_NAME}" == "Windows 32-bit" ]; then
|
||||
export PLATFORM="x86";
|
||||
elif [ "${TRAVIS_CPU_ARCH}" == "amd64" ]; then
|
||||
export PLATFORM="x86_64";
|
||||
else
|
||||
export PLATFORM="${TRAVIS_CPU_ARCH}";
|
||||
fi
|
||||
- source src/travis/${TRAVIS_OS_NAME}-before-install.sh
|
||||
|
||||
install:
|
||||
- source src/travis/${TRAVIS_OS_NAME}-install.sh
|
||||
|
||||
script:
|
||||
# Discover and run all Python unit tests. Buffer output so that it's not sent to the build log.
|
||||
- $python -m unittest discover --start-directory ./ --pattern "*_test.py" --buffer
|
||||
- touch $gampath/nobrowser.txt
|
||||
- $gam version extended
|
||||
- $gam version | grep travis # travis should be part of the path (not /tmp or such)
|
||||
# determine which Python version GAM is built with and ensure it's at least build version from above.
|
||||
- if [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]; then vline=$($gam version | grep "Python "); python_line=($vline); this_python=${python_line[1]}; $python tools/a_atleast_b.py $this_python $MIN_PYTHON_VERSION; fi
|
||||
# determine which OpenSSL version GAM is built with and ensure it's at least build version from above.
|
||||
- if [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]; then vline=$($gam version extended | grep "OpenSSL "); openssl_line=($vline); this_openssl=${openssl_line[1]}; $python tools/a_atleast_b.py $this_openssl $MIN_OPENSSL_VERSION; fi
|
||||
- if [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]; then $gam version extended | grep TLSv1\.[23]; fi # Builds should default TLS 1.2 or 1.3 to Google
|
||||
- if [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]; then GAM_TLS_MIN_VERSION=TLSv1_2 $gam version extended location tls-v1-0.badssl.com:1010; [[ $? == 3 ]]; fi # expect fail since server doesn't support our TLS version
|
||||
- export jid="$(cut -d'.' -f2 <<<"$TRAVIS_JOB_NUMBER")"
|
||||
- if [ "$TRAVIS_EVENT_TYPE" != "pull_request" ]; then export e2e=true; fi
|
||||
- if [ "$e2e" = true ]; then export gam_user=gam-travis-$jid@pdl.jaylee.us; fi
|
||||
- if [ "$e2e" = true ]; then openssl aes-256-cbc -K $encrypted_6294a53f809d_key -iv $encrypted_6294a53f809d_iv -in travis/creds.tar.enc -out travis/creds.tar -d; fi
|
||||
- if [ "$e2e" = true ]; then tar xvf travis/creds.tar -C $gampath; fi
|
||||
- if [ "$e2e" = true ]; then export OAUTHFILE=oauth2.txt-gam-travis-$jid; fi
|
||||
- if [ "$e2e" = true ]; then $gam oauth info; fi
|
||||
- if [ "$e2e" = true ]; then $gam info domain; fi
|
||||
- if [ "$e2e" = true ]; then $gam oauth refresh; fi
|
||||
- if [ "$e2e" = true ]; then $gam info user; fi
|
||||
- if [ "$e2e" = true ]; then export tstamp=$(date +%s%3N);
|
||||
export newbase=travis-test-$jid-$tstamp;
|
||||
export newuser=$newbase@pdl.jaylee.us;
|
||||
export newgroup=$newbase-group@pdl.jaylee.us;
|
||||
export newalias=$newbase-alias@pdl.jaylee.us;
|
||||
export newbuilding=$newbase-building;
|
||||
export newresource=$newbase-resource;
|
||||
export GAM_THREADS=5; fi
|
||||
- if [ "$e2e" = true ]; then echo email > sample.csv;
|
||||
for i in {01..20};
|
||||
do echo $newbase-bulkuser-$i >> sample.csv;
|
||||
done; fi
|
||||
- if [ "$e2e" = true ]; then $gam create user $newuser firstname Travis lastname $jid password random recoveryphone 12125121110 recoveryemail jay0lee@gmail.com travis.jid $jid; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user sendemail recipient $newuser subject "test message $newbase" message "Travis test message"; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user sendemail recipient exchange@pdl.jaylee.us subject "test ${tstamp}" message "test message"; fi
|
||||
- if [ "$e2e" = true ]; then $gam create group $newgroup name "Travis $jid group" description "This is a description" isarchived true; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser add license gsuitebusiness; fi
|
||||
- if [ "$e2e" = true ]; then $gam update group $newgroup add owner $gam_user; fi
|
||||
- if [ "$e2e" = true ]; then $gam update group $newgroup add member $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam create user ~~email~~ firstname "Travis Bulk" lastname ~~email~~ travis.jid $jid; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update user ~~email~~ recoveryphone 12125121110 recoveryemail jay0lee@gmail.com password random; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update user ~~email~~ recoveryphone "" recoveryemail ""; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user ~email add license gsuitebusiness; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user $gam_user sendemail recipient ~~email~~@pdl.jaylee.us subject "test message $newbase" message "Travis test message"; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update group $newgroup add member ~email; fi
|
||||
- if [ "$e2e" = true ]; then $gam info group $newgroup; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user check serviceaccount; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser imap on; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser show imap; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user $newuser delegate to ~email; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser show delegates; fi
|
||||
- if [ "$e2d" = true ]; then export biohazard=$(echo -e '\xe2\x98\xa3'); fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser label "$biohazard unicode biohazard $biohazard"; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser show labels; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser show labels > labels.txt; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user importemail subject "Travis import $newbase" message "This is a test import" labels IMPORTANT,UNREAD,INBOX,STARRED; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user insertemail subject "Travis insert $newbase" file gam.py labels INBOX,UNREAD; fi # yep body is gam code
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user sendemail subject "Travis send $gam_user $newbase" file gam.py recipient admin@pdl.jaylee.us; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user draftemail subject "Travis draft $newbase" message "Draft message test"; fi
|
||||
- if [ "$e2e" = true ]; then $gam users "$gam_user $newbase-bulkuser-01 $newbase-bulkuser-02 $newbase-bulkuser-03" delete messages query in:anywhere maxtodelete 99999 doit; fi
|
||||
- if [ "$e2e" = true ]; then $gam users "$newbase-bulkuser-04 $newbase-bulkuser-05 $newbase-bulkuser-06" trash messages query in:anywhere maxtotrash 99999 doit; fi
|
||||
- if [ "$e2e" = true ]; then $gam users "$newbase-bulkuser-07 $newbase-bulkuser-08 $newbase-bulkuser-09" modify messages query in:anywhere maxtomodify 99999 addlabel IMPORTANT addlabel STARRED doit; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser delete label --ALL_LABELS--; fi
|
||||
- if [ "$e2e" = true ]; then $gam create feature name Whiteboard-$newbase; fi
|
||||
- if [ "$e2e" = true ]; then $gam create feature name VC-$newbase; fi
|
||||
- if [ "$e2e" = true ]; then $gam create building "My Building - $newbase" id $newbuilding floors 1,2,3,4,5,6,7,8,9,10,11,12,14,15 description "No 13th floor here..."; fi
|
||||
- if [ "$e2e" = true ]; then $gam create resource $newresource "Resource Calendar $tstamp" capacity 25 features Whiteboard-$newbase,VC-$newbase building $newbuilding floor 15 type Room; fi
|
||||
- if [ "$e2e" = true ]; then $gam info resource $newresource; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $newuser show filelist; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user printacl | $gam csv - gam calendar $gam_user delete id ~id; fi # clear ACLs
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user update read domain; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user update freebusy default; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user add editor $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user showacl; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user printacl | $gam csv - gam calendar $gam_user delete id ~id; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user addevent summary "Travis test event" start $(date '+%FT%T.%N%:z' -d "now + 1 hour") end $(date '+%FT%T.%N%:z' -d "now + 2 hours") attendee $newgroup hangoutsmeet guestscanmodify true sendupdates all; fi
|
||||
- if [ "$e2e" = true ]; then $gam calendar $gam_user printevents after -0d; fi
|
||||
- if [ "$e2e" = true ]; then matterid=uid:$($gam create vaultmatter name "Travis matter $newbase" description "test matter" collaborators $newuser | head -1 | cut -d ' ' -f 3); fi
|
||||
- if [ "$e2e" = true ]; then $gam create vaulthold matter $matterid name "Travis hold $newbase" corpus mail accounts $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam print vaultmatters matterstate open; fi
|
||||
- if [ "$e2e" = true ]; then $gam print vaultholds matter $matterid; fi
|
||||
- if [ "$e2e" = true ]; then $gam create vaultexport matter $matterid name "Travis export $newbase" corpus mail accounts $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam print exports matter $matterid | $gam csv - gam info export $matterid id:~~id~~; fi
|
||||
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user ~email add calendar id:$newresource; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete resource $newresource; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete feature Whiteboard-$newbase; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete feature VC-$newbase; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete building $newbuilding; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete group $newgroup; fi
|
||||
- if [ "$e2e" = true ]; then $gam create alias $newalias user $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam whatis $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam user $gam_user show tokens; fi
|
||||
- if [ "$e2e" = true ]; then $gam print exports matter $matterid | $gam csv - gam download export $matterid id:~~id~~; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete hold "Travis hold $newbase" matter $matterid; fi
|
||||
- if [ "$e2e" = true ]; then $gam update matter $matterid action close; fi
|
||||
- if [ "$e2e" = true ]; then $gam update matter $matterid action delete; fi
|
||||
- if [ "$e2e" = true ]; then $gam delete user $newuser; fi
|
||||
- if [ "$e2e" = true ]; then $gam print users query "travis.jid=$jid" | $gam csv - gam delete user ~primaryEmail; fi
|
||||
- if [ "$e2e" = true ]; then $gam print mobile; fi
|
||||
- if [ "$e2e" = true ]; then $gam print devices; fi
|
||||
- if [ "$e2e" = true ]; then export sn="$jid$jid$jid$jid$jid-$(openssl rand -base64 32 | sed 's/[^a-zA-Z0-9]//g')"; fi
|
||||
- if [ "$e2e" = true ]; then $gam create device serialnumber $sn devicetype android; fi
|
||||
- if [ "$e2e" = true ]; then $gam print cros allfields nolists; fi
|
||||
- if [ "$e2e" = true ]; then $gam report usageparameters customer; fi
|
||||
- if [ "$e2e" = true ]; then $gam report usage customer parameters gmail:num_emails_sent,accounts:num_1day_logins; fi
|
||||
- if [ "$e2e" = true ]; then $gam report customer todrive; fi
|
||||
- if [ "$e2e" = true ]; then $gam report users fields accounts:is_less_secure_apps_access_allowed,gmail:last_imap_time,gmail:last_pop_time filters "accounts:last_login_time>2019-01-01T00:00:00.000Z" todrive; fi
|
||||
- if [ "$e2e" = true ]; then $gam report admin start -3d todrive; fi
|
||||
- if [ "$e2e" = true ]; then $gam print devices nopersonaldevices nodeviceusers filter "serial:$jid$jid$jid$jid$jid-" | $gam csv - gam delete device id ~name; fi
|
||||
- if ([ "$e2e" = true ] && [[ "$TRAVIS_JOB_NAME" != *"Testing" ]]); then
|
||||
for gamfile in gam-$GAMVERSION-*; do
|
||||
fileid=$($gam user $gam_user add drivefile localfile $gamfile drivefilename $GAMVERSION-${TRAVIS_COMMIT:0:7}-$gamfile parentid 1N2zbO33qzUQFsGM49-m9AQC1ijzd_ru1 returnidonly);
|
||||
$gam user $gam_user add drivefileacl $fileid anyone role reader withlink;
|
||||
done;
|
||||
fi
|
||||
|
||||
before_deploy:
|
||||
- export TRAVIS_TAG="preview"
|
||||
- unset LD_LIBRARY_PATH
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
token:
|
||||
secure: bzambMcQwyv/o5c5GrKGCsZHgE5R85tg8sNFvPfpISz3+uosCjnBXas7wvCKzT75XUFi2ztfbYak6HdKf4sGnNHk0saEicB3slH+ghPyZbYzp76yvvduhFO2nWW3/F01tL+Yfqqt4/q8wFaWGjrC5km+6GLVyB4lWA/Uyu49qKnz02uSwyhBD/VFbO7DOQ65a1iWk9HngyMsu0Oi7HIbSjSLtxTHedNfOf3waW0NivTTxYXiYGX/MCu3GWhgIGj47a+H3A6FcQ/9QWvnKgnoixdgPBUz7kDb7ktsWwQsILPGStgH7iMuG49ZlXdEFmqwifBri2wvzmFEevBGZjHcupy1IGrNFRG+IUGKMotio+OkLHlLjuv7ZJtqCz/Vf5SNFgNyMSanx6jKEUJuYvndVg99IRXmYVwHFwPu5BAcJACpU6C0AfyGmmSqqwxCd46uXL62ynxNFpHuRfOqlDnmCTfZgjOciJSlDDpf+Xz9fF7+oCoeCi3mrcZVFjhd3tT6Oxw5HrsDtm0ZNld1cdLidaq8H6vOFgHMd0A9yNYZzTzXTvpmxzkXT4Zc7s+PYKN6z5fRZ+pJeckUjRXblvVEfs5HFSymavcOc5AkRwxpvOsTQMNmlnaJCBo5UNs0K/rVmRi5cFmaiwTcBCY0kTllOBJ4zWsfq8seiokWwNUNK2g=
|
||||
file_glob: true
|
||||
overwrite: true
|
||||
file: gam-$GAMVERSION-*
|
||||
skip_cleanup: true
|
||||
draft: true
|
||||
on:
|
||||
repo: jay0lee/GAM
|
||||
condition: $TRAVIS_JOB_NAME != *"Testing"
|
||||
@@ -1,4 +1,6 @@
|
||||
GAM is a command line tool for Google Workspace (fka G Suite) Administrators to manage domain and user settings quickly and easily. [](https://travis-ci.org/jay0lee/GAM)
|
||||
GAM is a command line tool for Google Workspace (fka G Suite) Administrators to manage domain and user settings quickly and easily.
|
||||
|
||||

|
||||
# Quick Start
|
||||
## Linux / MacOS
|
||||
Open a terminal and run:
|
||||
@@ -12,8 +14,8 @@ Download the MSI Installer from the [GitHub Releases] page. Install the MSI and
|
||||
The GAM documentation is hosted in the [GitHub Wiki]
|
||||
# Mailing List / Discussion group
|
||||
The GAM mailing list / discussion group is hosted on [Google Groups]. You can join the list and interact via email, or just post from the web itself.
|
||||
# IM Room
|
||||
[](https://gitter.im/jay0lee-GAM/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
# Chat Room
|
||||
There is a public chat room hosted in Google Chat. [Instructions to join](https://git.io/gam-chat).
|
||||
# Author
|
||||
GAM is maintained by <a href="mailto:jay0lee@gmail.com">Jay Lee</a>. Please direct "how do I?" questions to [Google Groups].
|
||||
|
||||
|
||||
@@ -73,17 +73,9 @@ If an item contains spaces, it should be surrounded by ".
|
||||
<ProductID> ::=
|
||||
Google-Apps|
|
||||
Google-Chrome-Device-Management|
|
||||
Google-Coordinate|
|
||||
Google-Drive-storage|
|
||||
Google-Vault|
|
||||
101001|101005|101031
|
||||
<ProductID> ::=
|
||||
Google-Apps|
|
||||
Google-Chrome-Device-Management|
|
||||
Google-Coordinate|
|
||||
Google-Drive-storage|
|
||||
Google-Vault|
|
||||
101001|101005|101006|101031|101033|101034
|
||||
101001|101005|101031|101033|101034|101037
|
||||
<SKUID> ::=
|
||||
cloudidentity|identity|1010010001|
|
||||
cloudidentitypremium|identitypremium|1010050001|
|
||||
@@ -93,6 +85,13 @@ If an item contains spaces, it should be surrounded by ".
|
||||
gams|postini|gsuitegams|gsuitepostini|gsuitemessagesecurity|Google-Apps-For-Postini|
|
||||
gal|gsl|lite|gsuitelite|Google-Apps-Lite|
|
||||
gau|gsb|unlimited|gsuitebusiness|Google-Apps-Unlimited|
|
||||
gwep|workspaceeducationplus|1010310008|
|
||||
gwepstaff|workspaceeducationplusstaff|1010310009|
|
||||
gwepstudent|workspaceeducationplusstudent|1010310010|
|
||||
gwes|workspaceeducationstandard|1010310005|
|
||||
gwesstaff|workspaceeducationstandardstaff|1010310006|
|
||||
gwesstudent|workspaceeducationstandardstudent|1010310007|
|
||||
gwetlu|workspaceeducationupgrade|1010370001|
|
||||
wsentplus|workspaceenterpriseplus|gae|gse|enterprise|gsuiteenterprise|1010020020|
|
||||
wsbizplus|workspacebusinessplus|1010020025|
|
||||
wsentstan|workspaceenterprisestandard|'1010020026|
|
||||
@@ -103,7 +102,6 @@ If an item contains spaces, it should be surrounded by ".
|
||||
gsbau|businessarchived|gsuitebusinessarchived|
|
||||
gseau|enterprisearchived|gsuiteenterprisearchived|
|
||||
chrome|cdm|googlechromedevicemanagement|Google-Chrome-Device-Management|
|
||||
coordinate|googlecoordinate|Google-Coordinate|
|
||||
wsess|workspaceesentials|gsuiteessentials|essentials|d4e|driveenterprise|drive4enterprise|1010060001|
|
||||
wsentess|workspaceenterpriseessentials|1010060003|
|
||||
drive20gb|20gb|googledrivestorage20gb|Google-Drive-storage-20GB|
|
||||
@@ -149,7 +147,10 @@ If an item contains spaces, it should be surrounded by ".
|
||||
<AccessToken> ::= <String>
|
||||
<ACLScope> ::= [user:]<EmailAddress>|group:<EmailAddress>|domain[:<DomainName>]|default
|
||||
<APIScopeURL> ::= <String>
|
||||
<APPID> ::= <String>
|
||||
<ASPID> ::= <String>
|
||||
<AssetTag> ::= <String>
|
||||
<BrowserTokenPermanentID> ::= <String>
|
||||
<BuildingID> ::= <String>|id:<String>
|
||||
<CalendarACLRole> ::= editor|freebusy|freebusyreader|owner|reader|writer
|
||||
<CalendarACLRuleID> ::= user:<EmailAddress>|group:<EmailAddress>|domain:<DomainName>|default
|
||||
@@ -210,11 +211,10 @@ If an item contains spaces, it should be surrounded by ".
|
||||
<Password> ::= <String>
|
||||
<PermissionID> ::= id:<String>|<EmailAddress>|anyone|anyonewithlink
|
||||
<PrinterID> ::= <String>
|
||||
<PrintJobAge> ::= <Number>[m|h|d]
|
||||
<PrintJobID> ::= <String>
|
||||
<PrintJobStatus> ::= done|error|held|in_progress|queued|submitted
|
||||
<PropertyKey> ::= <String>
|
||||
<PropertyValue> ::= <String>
|
||||
<QueryBrowser> ::= <String> See: https://support.google.com/chrome/a/answer/9681204#retrieve_all_chrome_devices_for_an_account
|
||||
<QueryBrowserToken> ::= <String> See: https://support.google.com/chrome/a/answer/9949706?ref_topic=9301744
|
||||
<QueryCalendar> ::= <String>
|
||||
<QueryContact> ::= <String> See: https://developers.google.com/google-apps/contacts/v3/reference#contacts-query-parameters-reference
|
||||
<QueryCrOS> ::= <String> See: https://support.google.com/chrome/a/answer/1698333?hl=en
|
||||
@@ -222,8 +222,6 @@ If an item contains spaces, it should be surrounded by ".
|
||||
<QueryGmail> ::= <String> See: https://support.google.com/mail/answer/7190
|
||||
<QueryGroup> ::= <String> See: https://developers.google.com/admin-sdk/directory/v1/guides/search-groups
|
||||
<QueryMobile> ::= <String> See: https://support.google.com/a/answer/7549103
|
||||
<QueryPrinter> ::= <String> See: https://developers.google.com/cloud-print/docs/appInterfaces#search
|
||||
<QueryPrintJob> ::= <String> See: https://developers.google.com/cloud-print/docs/appInterfaces#parameters_3
|
||||
<QueryUser> ::= <String> See: https://developers.google.com/admin-sdk/directory/v1/guides/search-users
|
||||
<QueryVaultCorpus> ::= <String> See: https://developers.google.com/vault/reference/rest/v1/matters.holds#CorpusQuery
|
||||
<RequestID> ::= <String>
|
||||
@@ -492,9 +490,6 @@ If an item contains spaces, it should be surrounded by ".
|
||||
description|id|inherit|name|orgunitpath|parent|parentid|inherit
|
||||
<OrgUnitFieldNameList> ::= "<OrgUnitFieldName>(,<OrgUnitFieldName>)*"
|
||||
|
||||
<PrintJobOrderByFieldName> ::=
|
||||
create_time|status|title
|
||||
|
||||
<ResourceFieldName> ::=
|
||||
buildingid|
|
||||
capacity|
|
||||
@@ -568,6 +563,7 @@ Items, separated by spaces, with spaces, commas or single quotes in the items th
|
||||
<ACLList> ::= "<ACLScope>(,<ACLScope>)*"
|
||||
<APIScopeURLList> ::= "<APIScopeURL>(,<APIScopeURL>)*"
|
||||
<ASPIDList> ::= "<ASPID>(,<ASPID>)*"
|
||||
<AssetTagList> ::= "<AssetTag>(,<AssetTa>g)*"
|
||||
<CalendarList> ::= "<CalendarItem>(,<CalendarItem>)*"
|
||||
<ChatRoomList> ::= "<ChatRoom>(,<ChatRoom>)*"
|
||||
<CollaboratorItemList> ::= "<CollaboratorItem>(,<CollaboratorItem>)*"
|
||||
@@ -594,12 +590,10 @@ Items, separated by spaces, with spaces, commas or single quotes in the items th
|
||||
<MembersFieldNameList> ::= "<MembersFieldName>(,<MembersFieldName>)*"
|
||||
<MobileList> ::= "<MobileId>(,<MobileId>)*"
|
||||
<OrgUnitList> ::= "<OrgUnitPath>(,<OrgUnitPath>)*"
|
||||
<PrinterIDList> ::= "<PrinterID>(,<PrinterID>)*"
|
||||
<PrinterIDList> ::= "<PrinterID>)(,<PrinterID>)*"
|
||||
<ProductIDList> ::= "(<ProductID>|SKUID>)(,<ProductID>|SKUID>)*"
|
||||
<PrintJobIDList> ::= "<PrintJobID>(,<PrintJobID>)*"
|
||||
<QueryCrOSList> ::= "<QueryCrOS>(,<QueryCrOS>)*"
|
||||
<QueryMobileList> ::= "<QueryMobile>(,<QueryMobile>)*"
|
||||
<QueryPrinterList> ::= "<QueryPrinter>(,<QueryPrinter>)*"
|
||||
<QueryUserList> ::= "<QueryUser>(,<QueryUser>)*"
|
||||
<ResourceIDList> ::= "<ResourceID>(,<ResourceID>)*"
|
||||
<SKUIDList> ="<SKUID>(,<SKUID>)*"
|
||||
@@ -653,7 +647,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
|
||||
## Item attributes
|
||||
|
||||
<BuildingAttributes> ::=
|
||||
<BuildingAttribute> ::=
|
||||
(description <String>)|
|
||||
(floors <FloorNameList>)|
|
||||
(id <String>)|
|
||||
@@ -661,7 +655,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
(longitude <Float>)|
|
||||
(name <String>)
|
||||
|
||||
<CalendarAttributes> ::=
|
||||
<CalendarAttribute> ::=
|
||||
(selected <Boolean>)|(hidden <Boolean>)|(summary <String>)|(colorindex|colorid <CalendarColorIndex>)|(backgroundcolor <ColorValue>)|(foregroundcolor <ColorValue>)|
|
||||
(reminder clear|(email|sms|pop <Number>))|
|
||||
(notification clear|(email|sms eventcreation|eventchange|eventcancellation|eventresponse|agenda))
|
||||
@@ -669,7 +663,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
<CalendarSettings> ::=
|
||||
(summary <String>)|(description <String>)|(location <String>)|(timezone <TimeZone>)
|
||||
|
||||
<CourseAttributes> ::=
|
||||
<CourseAttribute> ::=
|
||||
(description <String>)|
|
||||
(heading <String>)|
|
||||
(name <String>)|
|
||||
@@ -678,14 +672,18 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
(state|status <CourseState>)|
|
||||
(owner|ownerid|teacher <UserItem>)
|
||||
|
||||
<CrOSAttributes> ::=
|
||||
<CrOSAttribute> ::=
|
||||
(asset|assetid|tag <String>)|
|
||||
(location <String>)|
|
||||
(notes <String>)|
|
||||
(org|ou <OrgUnitPath>)|
|
||||
(user <Name>)
|
||||
|
||||
<DriveFileAddAttributes> ::=
|
||||
<CIGroupAttribute> ::=
|
||||
(description <String>)|
|
||||
(name <String>)
|
||||
|
||||
<DriveFileAddAttribute> ::=
|
||||
(localfile <FileName>)|
|
||||
(convert)|(ocr)|(ocrlanguage <Language>)|
|
||||
(restricted|restrict)|(starred|star)|(trashed|trash)|(viewed|view)|
|
||||
@@ -693,9 +691,9 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
(contentrestrictions readonly true [reason <String>])|
|
||||
copyrequireswriterpermission|
|
||||
(lastviewedbyme <Time>)|(modifieddate|modifiedtime <Time>)|(description <String>)|(mimetype <MimeType>)|
|
||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|
|
||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|writerscanshare
|
||||
(shortcut <DriveFileID>)
|
||||
<DriveFileUpdateAttributes> ::=
|
||||
<DriveFileUpdateAttribute> ::=
|
||||
(localfile <FileName>)|
|
||||
(convert)|(ocr)|(ocrlanguage <Language>)|
|
||||
(restricted|restrict <Boolean>)|(starred|star <Boolean>)|(trashed|trash <Boolean>)|(viewed|view <Boolean>)|
|
||||
@@ -703,7 +701,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
(contentrestrictions readonly true [reason <String>])|
|
||||
(copyrequireswriterpermission <Boolean>)|
|
||||
(lastviewedbyme <Time>)|(modifieddate <Time>)|(description <String>)|(mimetype <MimeType>)|
|
||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|
|
||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|writerscanshare
|
||||
(shortcut <DriveFileID>)
|
||||
<GroupSettingsAttribute> ::=
|
||||
(allowexternalmembers <Boolean>)|
|
||||
@@ -773,13 +771,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
<MobileAction> ::=
|
||||
admin_remote_wipe|wipe|admin_account_wipe|accountwipe|wipeaccount|approve|block|cancel_remote_wipe_then_activate|cancel_remote_wipe_then_block
|
||||
|
||||
<PrinterAttributes> ::= (currentquota <Number>)|(dailyquota <Number>)|
|
||||
(defaultdisplayname <String>)|(description <String>)|(displayname <String>)|(firmware <String>)|(gcpversion <String>)|
|
||||
(istosaccepted <Boolean>)|(manufacturer <String>)|(model <String>)|(name <String>)|(ownerid <EmailAddress>)|(proxy <String>)|(public <Boolean>)|
|
||||
(quotaenabled <Boolean>)|(status <Number>)|(type <String>)|(uuid <String>)|
|
||||
(setupurl <URL>)|(supporturl <URL>)|(updateurl <URL>)
|
||||
|
||||
<ResourceAttributes> ::=
|
||||
<ResourceAttribute> ::=
|
||||
(buildingid <BuildingID>)|
|
||||
(capacity <Number>)|
|
||||
(category other|room|conference_room|category_unknown)|
|
||||
@@ -813,7 +805,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
||||
(recoveryphone <string>)|
|
||||
(suspended <Boolean>)|
|
||||
(<SchemaName>.<FieldName> [multivalued|multivalue|value|multinonempty [type home|other|work|(custom <String>)]] <String>)
|
||||
<UserMultiAttributes> ::=
|
||||
<UserMultiAttribute> ::=
|
||||
(address clear|(type home|other|work|(custom <String>) [unstructured|formatted <String>] [pobox <String>] [extendedaddress <String>] [streetaddress <String>]
|
||||
[locality <String>] [region <String>] [postalcode <String>] [country <String>] [countrycode <String>] notprimary|primary))|
|
||||
(otheremail clear|(home|other|work|<String> <String>))|
|
||||
@@ -872,7 +864,7 @@ gam <UserTypeEntity> check serviceaccount [scope|scopes <APIScopeURLList>]
|
||||
|
||||
gam whatis <EmailItem>
|
||||
|
||||
<ResoldCustomerAttributes> ::=
|
||||
<ResoldCustomerAttribute> ::=
|
||||
(email|alternateemail <EmailAddress>)|
|
||||
(contact|contactname <String>)|
|
||||
(phone|phonenumber <String>)|
|
||||
@@ -885,7 +877,7 @@ gam whatis <EmailItem>
|
||||
(zipcode|postal|postalcode <String>)|
|
||||
(country|countrycode <String>)
|
||||
|
||||
gam create resoldcustomer <CustomerDomain> (customer_auth_token <String>) <ResoldCustomerAttributes>+
|
||||
gam create resoldcustomer <CustomerDomain> (customer_auth_token <String>) <ResoldCustomerAttribute>+
|
||||
gam update resoldcustomer <CustomerID> [customer_auth_token <String>] <ResoldCustomerAttribues>+
|
||||
gam info resoldcustomer <CustomerID>
|
||||
|
||||
@@ -976,7 +968,7 @@ gam delete domainalias|aliasdomain <DomainAlias>
|
||||
gam info domainalias|aliasdomain <DomainAlias>
|
||||
gam print domainaliases|aliasdomains [todrive]
|
||||
|
||||
<CustomerAttributes> ::=
|
||||
<CustomerAttribute> ::=
|
||||
(primary <DomainName>)|
|
||||
(adminsecondaryemail|alternateemail <EmailAddress>)|
|
||||
(contact|contactname <String>)|
|
||||
@@ -991,7 +983,7 @@ gam print domainaliases|aliasdomains [todrive]
|
||||
(zipcode|postal|postalcode <String>)|
|
||||
(country|countrycode <String>)
|
||||
|
||||
gam update customer <CustomerAttributes>*
|
||||
gam update customer <CustomerAttribute>*
|
||||
|
||||
gam info customer
|
||||
|
||||
@@ -1019,9 +1011,9 @@ gam delete alias|nickname [user|group|target] <UniqueID>|<EmailAddress>
|
||||
gam info alias|nickname <EmailAddress>
|
||||
gam print aliases|nicknames [todrive] [shownoneditable] [nogroups] [nousers] [(query <QueryUser>)|(queries <QueryUserList)]
|
||||
|
||||
gam calendar <CalendarItem> add <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain [<DomainName>])|default [sendnotifications <Boolean>]
|
||||
gam calendar <CalendarItem> update <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain [<DomainName>])|default [sendnotifications <Boolean>]
|
||||
gam calendar <CalendarItem> del|delete <CalendarACLRole> <EmailAddress>|(domain [<DomainName>])|default
|
||||
gam calendar <CalendarItem> add <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>) [sendnotifications <Boolean>]
|
||||
gam calendar <CalendarItem> update <CalendarACLRole> ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domain|default [sendnotifications <Boolean>]
|
||||
gam calendar <CalendarItem> del|delete ([user] <EmailAddress>)|(group <EmailAddress>)|(domain <DomainName>)|domainx|default
|
||||
gam calendar <CalendarItem> del|delete id <CalendarACLRuleID>
|
||||
gam calendar <CalendarItem> showacl
|
||||
gam calendar <CalendarItem> printacl [todrive]
|
||||
@@ -1034,7 +1026,7 @@ The following attributes are equivalent:
|
||||
sendnotifications false - sendupdates none
|
||||
sendnotifications true - sendupdates all
|
||||
|
||||
<EventAttributes> ::=
|
||||
<EventAttribute> ::=
|
||||
anyonecanaddself|
|
||||
(attendee <EmailAddress>)|
|
||||
available|
|
||||
@@ -1060,8 +1052,8 @@ The following attributes are equivalent:
|
||||
(timezone <Timezone>)|
|
||||
(visibility default|public|prvate)
|
||||
|
||||
<EventUpdateAttributes> ::=
|
||||
<EventAttributes>|
|
||||
<EventUpdateAttribute> ::=
|
||||
<EventAttribute>|
|
||||
(removeattendee <EmailAddress>)|
|
||||
(replacedescription <RegularExpression> <String>)
|
||||
|
||||
@@ -1076,10 +1068,10 @@ The following attributes are equivalent:
|
||||
<EventDisplayProperty> ::=
|
||||
(timezone <TimeZone>)
|
||||
|
||||
gam calendar <CalendarItem> addevent [id <String>] <EventAttributes>+ [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> addevent [id <String>] <EventAttribute>+ [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> deleteevent id|eventid <EventID> [doit] [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> moveevent id|eventid <EventID> [doit] [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> updateevent <EventID> <EventUpdateAttributes>+ [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> updateevent <EventID> <EventUpdateAttribute>+ [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> wipe
|
||||
gam calendar <CalendarItem> printevents <EventSelectProperty>* <EventDisplayProperty>* [todrive]
|
||||
|
||||
@@ -1091,6 +1083,86 @@ gam calendar <CalendarItem> printevents <EventSelectProperty>* <EventDisplayProp
|
||||
|
||||
gam calendar <CalendarItem> modify <CalendarSettings>+
|
||||
|
||||
<BrowserAttribute> ::=
|
||||
(assetid <String>)|
|
||||
(location <String>)|
|
||||
(notes <String>)|
|
||||
(user <String>
|
||||
|
||||
<BrowserFieldName> ::=
|
||||
annotatedAssetId|
|
||||
annotatedLocation|
|
||||
annotatedNotes|
|
||||
annotatedUser|
|
||||
browsers|
|
||||
browserVersions|
|
||||
deviceId|
|
||||
extensionCount|
|
||||
installedBrowserVersion|
|
||||
lastActivityTime|
|
||||
lastDeviceUser|
|
||||
lastDeviceUsers|
|
||||
lastPolicyFetchTime|
|
||||
lastRegistrationTime|
|
||||
lastStatusReportTime|
|
||||
machineName|
|
||||
machinePolicies|
|
||||
orgUnitPath|
|
||||
osArchitecture|
|
||||
osPlatform|
|
||||
osPlatformVersion|
|
||||
osVersion|
|
||||
orgUnitPath|
|
||||
policyCount|
|
||||
safeBrowsingClickThroughCount|
|
||||
serialNumber|
|
||||
virtualDeviceId
|
||||
<BrowserFieldNameList> ::= "<BrowseFieldName>(,<BrowserFieldName>)*"
|
||||
|
||||
gam move browsers ou|org|orgunit <OrgUnitPath>
|
||||
((ids <DeviceIDList>) |
|
||||
(query <QueryBrowser>) |
|
||||
(file <FileName>) |
|
||||
(csvfile <FileName>:<FieldName>))
|
||||
[batchsize <Integer>]
|
||||
gam update browser <DeviceID> <BrowserAttibute>+
|
||||
|
||||
gam info browser <DeviceID>
|
||||
[basic|full]
|
||||
[fields <BrowserFieldNameList>]
|
||||
|
||||
gam print browsers [todrive]
|
||||
[ou|org|orgunit <OrgUnitPath>] [query <QueryBrowser>]
|
||||
[projection basic|full]
|
||||
[fields <BrowserFieldNameList>]
|
||||
[sortheaders]
|
||||
|
||||
gam create browsertoken
|
||||
[ou|org|orgunit <OrgUnitPath>] [expire|expires <Time>]
|
||||
gam revoke browsertoken <BrowserTokenPermanentID>
|
||||
|
||||
<BrowserTokenFieldName> ::=
|
||||
createTime|
|
||||
creatorId|
|
||||
customerId|
|
||||
expireTime|
|
||||
orgUnitPath|
|
||||
revokeTime|
|
||||
revokerId|
|
||||
state|
|
||||
token|
|
||||
tokenPermanentId
|
||||
<BrowserTokenFieldNameList> ::= "<BrowseTokenFieldName>(,<BrowserTokenFieldName>)*"
|
||||
|
||||
gam show browsertokens
|
||||
[query <QueryBrowserToken>]
|
||||
[fields <BrowserTokenFieldNameList>]
|
||||
|
||||
gam print browsertokens [todrive]
|
||||
[query <QueryBrowserToken>]
|
||||
[fields <BrowserTokenFieldNameList>]
|
||||
[sortheaders]
|
||||
|
||||
<CrOSAction> ::=
|
||||
deprovision_same_model_replace|
|
||||
deprovision_different_model_replace|
|
||||
@@ -1099,7 +1171,19 @@ gam calendar <CalendarItem> modify <CalendarSettings>+
|
||||
disable|
|
||||
reenable
|
||||
|
||||
gam update cros <CrOSEntity> (<CrOSAttributes>+)|(action <CrOSAction> [acknowledge_device_touch_requirement])
|
||||
gam update cros <CrOSEntity> action <CrOSAction> [acknowledge_device_touch_requirement]
|
||||
|
||||
<CrOSCommand>
|
||||
wipe_users|
|
||||
remote_powerwash|
|
||||
reboot|
|
||||
set_volume <0-100>|
|
||||
take_a_screenshot
|
||||
|
||||
gam issuecommand cros <CrOSEntity> command <CrOSCommand> [times_to_check_status <0-1000+>] [doit]
|
||||
gam getcommand cros <CrOSEntity> commandid <CommandID> [times_to_check_status <0-1000+>]
|
||||
|
||||
gam update cros <CrOSEntity> <CrOSAttribute>+
|
||||
gam info cros <CrOSEntity> [nolists] [listlimit <Number>] [start <Date>] [end <Date>]
|
||||
[basic|full|allfields] <CrOSFieldName>* [fields <CrOSFieldNameList>] [downloadfile latest|<Time>] [targetfolder <FilePath>]
|
||||
|
||||
@@ -1147,6 +1231,11 @@ The listlimit <Number> argument limits the number of recent users, time ranges a
|
||||
The start <Date> and end <Date> arguments filter the time ranges.
|
||||
Delimiter defaults to comma.
|
||||
|
||||
gam delete chromepolicy <SchemaName>+ ou|org|orgunit <OrgUnitItem> [(printerid <PrinterID>)|(appid <AppID>)]
|
||||
gam update chromepolicy (<SchemaName> (<Field> <Value>)+)+ ou|org|orgunit <OrgUnitItem> [(printerid <PrinterID>)|(appid <AppID>)]
|
||||
gam show chromepolicy ou|org|orgunit <OrgUnitItem> [(printerid <PrinterID>)|(appid <AppID>)]
|
||||
gam show chromeschema [filter <String>]
|
||||
|
||||
<DeviceID> ::= devices/<String>
|
||||
<DeviceType> ::= android|chrome_os|google_sync|ios|linux|mac_os|windows
|
||||
<DeviceUserID> ::= devices/<String>/deviceUsers/<String>
|
||||
@@ -1158,11 +1247,6 @@ gam info device [id] <DeviceID>
|
||||
gam delete device [id] <DeviceID>
|
||||
gam cancelwipe device [id] <DeviceID>
|
||||
gam wipe device [id] <DeviceID>
|
||||
gam approve deviceuser [id] <DeviceUserID>
|
||||
gam block deviceuser [id] <DeviceUserID>
|
||||
gam delete deviceuser [id] <DeviceUserID>
|
||||
gam cancelwipe deviceuser [id] <DeviceUserID>
|
||||
gam wipe deviceuser [id] <DeviceUserID>
|
||||
gam print devices [todrive] [filter|query <QueryDevice>]
|
||||
[orderby <DeviceOrderByFieldName> [ascending|descending]]
|
||||
[company|personal|nocompanydevices|nopersonaldevices]
|
||||
@@ -1175,14 +1259,65 @@ gam sync devices [filter|query <QueryDevice>]
|
||||
[unassigned_missing_action delete|wipe|donothing]
|
||||
[assigned_missing_action delete|wipe|donothing]
|
||||
|
||||
gam approve deviceuser [id] <DeviceUserID>
|
||||
gam block deviceuser [id] <DeviceUserID>
|
||||
gam delete deviceuser [id] <DeviceUserID>
|
||||
gam cancelwipe deviceuser [id] <DeviceUserID>
|
||||
gam wipe deviceuser [id] <DeviceUserID>
|
||||
|
||||
gam info deviceuserstate [id] <DeviceUserID> [clientid <String>]
|
||||
gam update deviceuserstate [id] <DeviceUserID> [clientid <String>]
|
||||
[customid <String>] [assettags clear|<AssetTagList>]
|
||||
[compliantstate|compliancestate compliant|noncompliant] [managedstate clear|managed|unmanaged]
|
||||
[healthscore very_poor|poor|neutral|good|very_good] [scorereason clear|<String>]
|
||||
(customvalue (bool <Boolean>)|(number <Integer>)|(string <String>))*
|
||||
|
||||
gam update mobile <MobileID>|query:<QueryMobile> action <MobileAction> [doit] [if_users|match_users <UserTypeEntity>]
|
||||
gam delete mobile <MobileID>
|
||||
gam info mobile <MobileID>
|
||||
gam print mobile [todrive] [(query <QueryMobile>)|(queries <QueryMobileList>)] [basic|full] [orderby <MobileOrderByFieldName> [ascending|descending]]
|
||||
fields <MobileFieldNameList>] [delimiter <Character>] [appslimit <Number>] [listlimit <Number>]
|
||||
|
||||
gam create group <EmailAddress> <GroupAttributes>*
|
||||
gam update group <GroupItem> [email <EmailAddress>] <GroupAttributes>*
|
||||
<PrinterAttribute> ::=
|
||||
(description <String>)|
|
||||
(displayname <String>)|
|
||||
(makeandmodel <String>)|
|
||||
(ou|org|orgunit|orgunitid <OrgUnitItem>)|
|
||||
(ownerid <EmailAddress>)|
|
||||
(uri <String>)|
|
||||
(driverless|usedriverlessconfig)
|
||||
|
||||
gam create printer <PrinterAttribute>+
|
||||
gam update printer <PrinterID> <PrinterAttribute>+
|
||||
gam delete printer <PrinterIDList>|(file <FileName>)|(csvfile <FileName>:<FieldName>)
|
||||
|
||||
gam info printer <PrinterID>
|
||||
gam print printers [todrive] [filter <String>]
|
||||
gam print printermodels [todrive] [filter <String>]
|
||||
|
||||
gam create cigroup <EmailAddress> <CIGroupAttribute>*
|
||||
[makeowner] [alias|aliases <AliasList>] [dynamic <QueryDynamicGroup>]
|
||||
gam update cigroup <GroupItem> [email <EmailAddress>] <CIGroupAttribute>* [security]
|
||||
gam update cigroup <GroupItem> add [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
|
||||
gam update cigroup <GroupItem> delete|remove [owner|manager|member] [notsuspended|suspended] <UserTypeEntity>
|
||||
gam update cigroup <GroupItem> sync [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
|
||||
gam update cigroup <GroupItem> update [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
|
||||
gam update cigroup <GroupItem> clear [member] [manager] [owner] [notsuspended|suspended]
|
||||
gam delete cigroup <GroupItem>
|
||||
gam info cigroup <GroupItem> [nousers] [nojoindate] [showupdatedate]
|
||||
|
||||
gam print cigroups [todrive]
|
||||
[enterprisemember <UserItem>]
|
||||
[members|memberscount] [managers|managerscount] [owners|ownerscount]
|
||||
[delimiter <Character>] [sortheaders]
|
||||
|
||||
gam info cimember <UserItem> <GroupItem>
|
||||
gam print cigroup-members|cigroups-members [todrive]
|
||||
[(enterprisemember <UserItem>)|(cigroup <GroupItem>)]
|
||||
[roles <GroupRoleList>]
|
||||
|
||||
gam create group <EmailAddress> <GroupAttribute>*
|
||||
gam update group <GroupItem> [email <EmailAddress>] <GroupAttribute>*
|
||||
gam update group <GroupItem> add [owner|manager|member] [notsuspended|suspended] [allmail|daily|digest|none|nomail] <UserTypeEntity>
|
||||
gam update group <GroupItem> delete|remove [owner|manager|member] <UserTypeEntity>
|
||||
gam update group <GroupItem> sync [owner|manager|member] [notsuspended|suspended] [allmail|daily|digest|none|nomail] <UserTypeEntity>
|
||||
@@ -1202,11 +1337,20 @@ gam print group-members|groups-members [todrive]
|
||||
[roles <GroupRoleList>] [membernames] [fields <MembersFieldNameList>]
|
||||
[includederivedmembership]
|
||||
|
||||
gam send userinvitation <EmailAddress>
|
||||
gam cancel userinvitation <EmailAddress>
|
||||
gam check userinvitation|isinvitable <EmailAddress>
|
||||
gam info userinvitation <EmailAddress>
|
||||
gam print userinvitations [todrive]
|
||||
[state notyetsent|invited|accepted|declined]]
|
||||
[orderby email|updatetime [ascending|descending]]
|
||||
gam <UserTypeEntity> check isinvitable [todrive]
|
||||
|
||||
gam print licenses [todrive] [(products|product <ProductIDList>)|(skus|sku <SKUIDList>)|allskus|gsuite] [countsonly]
|
||||
gam show license|licenses|licence|licences [(products|product <ProductIDList>)|(skus|sku <SKUIDList>)|allskus|gsuite]
|
||||
|
||||
gam create building <Name> <BuildingAttributes>*
|
||||
gam update building <BuildIngID> <BuildingAttributes>*
|
||||
gam create building <Name> <BuildingAttribute>*
|
||||
gam update building <BuildIngID> <BuildingAttribute>*
|
||||
gam delete building <BuildingID>
|
||||
gam info building <BuildingID>
|
||||
gam print buildings [todrive]
|
||||
@@ -1216,8 +1360,8 @@ gam update feature <Name> name <Name>
|
||||
gam delete feature <Name>
|
||||
gam print features [todrive]
|
||||
|
||||
gam create resource <ResourceID> <Name> <ResourceAttributes>*
|
||||
gam update resource <ResourceID> <ResourceAttributes>*
|
||||
gam create resource <ResourceID> <Name> <ResourceAttribute>*
|
||||
gam update resource <ResourceID> <ResourceAttribute>*
|
||||
gam delete resource <ResourceID>
|
||||
gam info resource <ResourceID>
|
||||
gam print resources [todrive] [allfields] <ResourceFieldName>* [query <String>]
|
||||
@@ -1229,8 +1373,8 @@ gam info schema <SchemaName>
|
||||
gam show schema|schemas
|
||||
gam print schema|schemas
|
||||
|
||||
gam create user <EmailAddress> <UserAttributes>*
|
||||
gam update user <UserItem> <UserAttributes>* [clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>]
|
||||
gam create user <EmailAddress> <UserAttribute>*
|
||||
gam update user <UserItem> <UserAttribute>* [clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>]
|
||||
gam delete user <UserItem>
|
||||
gam undelete user <UserItem> [org|ou <OrgUnitPath>]
|
||||
gam info user [<UserItem>] [noaliases] [nogroups] [nolicenses|nolicences] [noschemas] [schemas|custom <SchemaNameList>] [userview] [skus|sku <SKUIDList>]
|
||||
@@ -1251,8 +1395,8 @@ gam create verify|verification <DomainName>
|
||||
gam update verify|verification <DomainName> cname|txt|text|site|file
|
||||
gam info verify|verification
|
||||
|
||||
gam create course [id|alias <CourseAlias>] <CourseAttributes>*
|
||||
gam update course <CourseID> <CourseAttributes>+
|
||||
gam create course [id|alias <CourseAlias>] <CourseAttribute>*
|
||||
gam update course <CourseID> <CourseAttribute>+
|
||||
gam delete course <CourseID>
|
||||
gam info course <CourseID>
|
||||
gam print courses [todrive] [teacher <UserItem>] [student <UserItem>] [states <CourseStateList>]
|
||||
@@ -1272,32 +1416,6 @@ gam show guardian|guardians [invitedguardian <EmailAddress>] [student <StudentIt
|
||||
gam print guardian|guardians [todrive] [invitedguardian <EmailAddress>] [student <StudentItem>] [invitations [states <GuardianStateList>]] [<UserTypeEntity>]
|
||||
gam cancel guardianinvitation|guardianinvitations <GuardianInvitationID> <StudentItem>
|
||||
|
||||
gam update printer <PrinterID> <PrinterAttributes>+
|
||||
gam delete printer <PrinterID>
|
||||
gam info printer <PrinterID> [everything]
|
||||
gam print printers [todrive] [(query <QueryPrinter>)|(queries <QueryPrinterList>)] [type <String>] [status <String>] [extrafields <String>]
|
||||
|
||||
gam printer <PrinterID> add user|manager|owner <EmailAddress>|[domain:]<DomainName>|public [notify]
|
||||
gam printer <PrinterID> delete <EmailAddress>|[domain:]<DomainName>|public
|
||||
gam printer <PrinterID> showacl
|
||||
gam printjob <PrintJobID> cancel
|
||||
gam printjob <PrintJobID> delete
|
||||
gam printjob <PrintJobID> resubmit <PrinterID>
|
||||
|
||||
gam printjob <PrinterID>|any fetch
|
||||
[olderthan|newerthan <PrintJobAge>] [query <QueryPrintJob>]
|
||||
[status <PrintJobStatus>]
|
||||
[orderby <PrintJobOrderByFieldName> [ascending|descending]]
|
||||
[owner|user <EmailAddress>]
|
||||
[limit <Number>] [drivedir|(targetfolder <FilePath>)]
|
||||
gam printjob <PrinterID> submit <FileName>|<URL> [name|title <String>] (tag <String>)*
|
||||
gam print printjobs [todrive] [printer|printerid <PrinterID>]
|
||||
[olderthan|newerthan <PrintJobAge>] [query <QueryPrintJob>]
|
||||
[status <PrintJobStatus>]
|
||||
[orderby <PrintJobOrderByFieldName> [ascending|descending]]
|
||||
[owner|user <EmailAddress>]
|
||||
[limit <Number>]
|
||||
|
||||
gam create vaultexport|export matter <MatterItem> [name <name>] corpus <drive|mail|groups|hangouts_chat>
|
||||
(accounts <EmailAddressList>) | (orgunit|ou <OrgUnitPath>) | (teamdrives <TeamDriveList>) | (rooms <ChatRoomList>) | everyone
|
||||
[scope <all_data|held_data|unprocessed_data>]
|
||||
@@ -1333,6 +1451,18 @@ gam undelete vaultmatter|matter <MatterItem>
|
||||
gam info vaultmatter|matter <MatterItem>
|
||||
gam print vaultmatters|matters [todrive] [basic|full] [matterstate open|closed|deleted]
|
||||
|
||||
gam print vaultcounts [todrive]
|
||||
matter <MatterItem> corpus mail|groups
|
||||
(accounts <EmailAddressEntity>) | (orgunit|org|ou <OrgUnitPath>) | everyone
|
||||
[scope <all_data|held_data|unprocessed_data>]
|
||||
[terms <terms>] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] [timezone <TimeZone>]
|
||||
[excludedrafts <Boolean>]
|
||||
[wait <Integer>]
|
||||
|
||||
gam print vaultcounts [todrive]
|
||||
matter <MatterItem> operation <String>
|
||||
[wait <Integer>]
|
||||
|
||||
gam <UserTypeEntity> delete|del asp|asps|applicationspecificpasswords all|<ASPIDList>
|
||||
gam <UserTypeEntity> show asps|asp|applicationspecificpasswords
|
||||
|
||||
@@ -1340,8 +1470,8 @@ gam <UserTypeEntity> update backupcodes|backupcode|verificationcodes
|
||||
gam <UserTypeEntity> delete|del backupcodes|backupcode|verificationcodes
|
||||
gam <UserTypeEntity> show backupcodes|backupcode|verificationcodes
|
||||
|
||||
gam <UserTypeEntity> add calendar <CalendarItem> <CalendarAttributes>*
|
||||
gam <UserTypeEntity> update calendar <CalendarItem>|primary <CalendarAttributes>+
|
||||
gam <UserTypeEntity> add calendar <CalendarItem> <CalendarAttribute>*
|
||||
gam <UserTypeEntity> update calendar <CalendarItem>|primary <CalendarAttribute>+
|
||||
gam <UserTypeEntity> delete|del calendar <CalendarItem>
|
||||
gam <UserTypeEntity> show calendars
|
||||
gam <UserTypeEntity> info calendar <CalendarItem>|primary
|
||||
@@ -1360,8 +1490,8 @@ gam <UserTypeEntity> show fileinfo <DriveFileID> [allfields|<DriveFieldName>*]
|
||||
gam <UserTypeEntity> show filerevisions <DriveFileID>
|
||||
gam <UserTypeEntity> show filetree [anyowner] (orderby <DriveOrderByFieldName> [ascending|descending])*
|
||||
|
||||
gam <UserTypeEntity> create|add drivefile [drivefilename <DriveFileName>] <DriveFileAddAttributes>* [csv] [todrive] [returnidonly]
|
||||
gam <UserTypeEntity> update drivefile (id <DriveFileID)|(drivefilename <DriveFileName>)|(query <QueryDriveFile) [copy] [newfilename <DriveFileName>] <DriveFileUpdateAttributes>*
|
||||
gam <UserTypeEntity> create|add drivefile [drivefilename <DriveFileName>] <DriveFileAddAttribute>* [csv] [todrive] [returnidonly]
|
||||
gam <UserTypeEntity> update drivefile (id <DriveFileID)|(drivefilename <DriveFileName>)|(query <QueryDriveFile) [copy] [newfilename <DriveFileName>] <DriveFileUpdateAttribute>*
|
||||
gam <UserTypeEntity> get drivefile (id <DriveFileID>)|(drivefilename <DriveFileName>)|(query <QueryDriveFile>)
|
||||
[revision <Number>] [(format <FileFormatList>)|(csvsheet <String>)]
|
||||
[targetfolder <FilePath>] [targetname -|<FileName>] [overwrite] [showprogress]
|
||||
@@ -1398,7 +1528,7 @@ gam <UserTypeEntity> show tokens|token [clientid <ClientID>]
|
||||
gam <UserTypeEntity> print tokens|token [todrive] [clientid <ClientID>]
|
||||
gam print tokens|token [todrive] [clientid <ClientID>] [<UserTypeEntity>]
|
||||
|
||||
gam <UserTypeEntity> update user <UserAttributes>
|
||||
gam <UserTypeEntity> update user <UserAttribute>
|
||||
|
||||
gam <UserTypeEntity> deprovision|deprov
|
||||
|
||||
@@ -1438,6 +1568,11 @@ gam <UserTypeEntity> delete|del delegate|delegates <EmailAddress>
|
||||
gam <UserTypeEntity> show delegates|delegate [csv]
|
||||
gam <UserTypeEntity> print delegates [todrive]
|
||||
|
||||
gam <UserTypeEntity> create|add contactdelegate <EmailAddress>
|
||||
gam <UserTypeEntity> delete|del contactdelegate <EmailAddress>
|
||||
gam <UserTypeEntity> show contactdelegates [csv]
|
||||
gam <UserTypeEntity> print contactdelegates [todrive]
|
||||
|
||||
gam <UserTypeEntity> [create|add] filter [from <EmailAddress>] [to <EmailAddress>] [subject <String>] [haswords|query <List>] [nowords|negatedquery <List>] [musthaveattachment|hasattachment] [excludechats] [size larger|smaller <ByteCount>]
|
||||
[label <LabelID>] [important|notimportant] [star] [trash] [markread] [archive] [neverspam] [forward <EmailAddress>]
|
||||
gam <UserTypeEntity> delete filters <FilterIDEntity>
|
||||
|
||||
587
src/cbcm-v1.1beta1.json
Normal file
587
src/cbcm-v1.1beta1.json
Normal file
@@ -0,0 +1,587 @@
|
||||
{
|
||||
"auth": {
|
||||
"oauth2": {
|
||||
"scopes": {
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers": {
|
||||
"description": "View and manage your Chrome browsers registered with Cloud Management"
|
||||
},
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly": {
|
||||
"description": "View your Chrome browsers registered with Cloud Management"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"basePath": "",
|
||||
"baseUrl": "https://www.googleapis.com/admin/directory/v1.1beta1/customer/",
|
||||
"batchPath": "batch",
|
||||
"canonicalName": "cbcm",
|
||||
"discoveryVersion": "v1",
|
||||
"documentationLink": "https://support.google.com/chrome/a/answer/9681204",
|
||||
"fullyEncodeReservedExpansion": true,
|
||||
"icons": {
|
||||
"x16": "http://www.google.com/images/icons/product/search-16.gif",
|
||||
"x32": "http://www.google.com/images/icons/product/search-32.gif"
|
||||
},
|
||||
"id": "cbcm:v1.1beta1",
|
||||
"kind": "discovery#restDescription",
|
||||
"mtlsRootUrl": "https://admin.mtls.googleapis.com/",
|
||||
"name": "cbcm",
|
||||
"ownerDomain": "google.com",
|
||||
"ownerName": "Jay Lee",
|
||||
"packagePath": "cbcm",
|
||||
"parameters": {
|
||||
"$.xgafv": {
|
||||
"description": "V1 error format.",
|
||||
"enum": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"v1 error format",
|
||||
"v2 error format"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"access_token": {
|
||||
"description": "OAuth access token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"alt": {
|
||||
"default": "json",
|
||||
"description": "Data format for response.",
|
||||
"enum": [
|
||||
"json",
|
||||
"media",
|
||||
"proto"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Responses with Content-Type of application/json",
|
||||
"Media download with context-dependent Content-Type",
|
||||
"Responses with Content-Type of application/x-protobuf"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"callback": {
|
||||
"description": "JSONP",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"description": "Selector specifying which fields to include in a partial response.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"oauth_token": {
|
||||
"description": "OAuth 2.0 token for the current user.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"prettyPrint": {
|
||||
"default": "true",
|
||||
"description": "Returns response with indentations and line breaks.",
|
||||
"location": "query",
|
||||
"type": "boolean"
|
||||
},
|
||||
"quotaUser": {
|
||||
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"uploadType": {
|
||||
"description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"upload_protocol": {
|
||||
"description": "Upload protocol for media (e.g. \"raw\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"protocol": "rest",
|
||||
"resources": {
|
||||
"chromebrowsers": {
|
||||
"methods": {
|
||||
"delete": {
|
||||
"description": "Deletes a browser.",
|
||||
"flatPath": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"httpMethod": "DELETE",
|
||||
"id": "cbcm.chromebrowsers.delete",
|
||||
"parameterOrder": [
|
||||
"customer",
|
||||
"deviceId"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"description": "Immutable ID of the browser.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"description": "Retrieves a browser.",
|
||||
"flatPath": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"httpMethod": "GET",
|
||||
"id": "cbcm.chromebrowsers.get",
|
||||
"parameterOrder": [
|
||||
"customer",
|
||||
"deviceId"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"description": "Immutable ID of the browser.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"projection": {
|
||||
"description": "Restrict information returned to a set of selected fields. FULL or BASIC.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"response": {
|
||||
"$ref": "ChromeBrowser"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers",
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly"
|
||||
]
|
||||
},
|
||||
"list": {
|
||||
"description": "Retrieves a paginated list of all the browsers in a domain.",
|
||||
"flatPath": "{customer}/devices/chromebrowsers",
|
||||
"httpMethod": "GET",
|
||||
"id": "cbcm.chromebrowsers.list",
|
||||
"parameterOrder": [
|
||||
"customer"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"maxResults": {
|
||||
"description": "Maximum number of results to return.",
|
||||
"format": "int32",
|
||||
"location": "query",
|
||||
"maximum": "100",
|
||||
"minimum": "1",
|
||||
"type": "integer"
|
||||
},
|
||||
"orderBy": {
|
||||
"description": "property to use for sorting results.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"orgUnitPath": {
|
||||
"description": "The full path of the organizational unit or its unique ID.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"pageToken": {
|
||||
"description": "Token to specify the next page in the list.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"projection": {
|
||||
"description": "Restrict information returned to a set of selected fields. FULL or BASIC.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"query": {
|
||||
"description": "Search string using the list page query language.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"sortOrder": {
|
||||
"description": "Whether to return results in ascending or descending order. Must be used with the orderBy parameter.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers",
|
||||
"response": {
|
||||
"$ref": "ChromeBrowsers"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers",
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly"
|
||||
]
|
||||
},
|
||||
"moveChromeBrowsersToOu": {
|
||||
"description": "Move Chrome Browsers Device between Organization Units",
|
||||
"flatPath": "{customer}/devices/chromebrowsers/moveChromeBrowsersToOu",
|
||||
"httpMethod": "POST",
|
||||
"id": "cbcm.chromebrowsers.moveChromeBrowsersToOu",
|
||||
"parameterOrder": [
|
||||
"customer"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers/moveChromeBrowsersToOu",
|
||||
"request": {
|
||||
"$ref": "MoveChromeBrowsersRequest"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
},
|
||||
"update": {
|
||||
"description": "Updates a browser.",
|
||||
"flatPath": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"httpMethod": "PUT",
|
||||
"id": "cbcm.chromebrowsers.update",
|
||||
"parameterOrder": [
|
||||
"customer",
|
||||
"deviceId"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"description": "Immutable ID of the browser.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"projection": {
|
||||
"description": "BASIC or FULL",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/devices/chromebrowsers/{deviceId}",
|
||||
"request": {
|
||||
"$ref": "ChromeBrowser"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "ChromeBrowser"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"enrollmentTokens": {
|
||||
"methods": {
|
||||
"list": {
|
||||
"description": "Retrieves a paginated list of all the browser entollment tokens in a domain.",
|
||||
"flatPath": "{customer}/chrome/enrollmentTokens",
|
||||
"httpMethod": "GET",
|
||||
"id": "cbcm.enrollmentTokens.list",
|
||||
"parameterOrder": [
|
||||
"customer"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"pageSize": {
|
||||
"description": "Maximum number of results to return.",
|
||||
"format": "int32",
|
||||
"location": "query",
|
||||
"maximum": "100",
|
||||
"minimum": "1",
|
||||
"type": "integer"
|
||||
},
|
||||
"orgUnitPath": {
|
||||
"description": "The full path of the organizational unit or its unique ID.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"pageToken": {
|
||||
"description": "Token to specify the next page in the list.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"query": {
|
||||
"description": "Search string using the list page query language.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/chrome/enrollmentTokens",
|
||||
"response": {
|
||||
"$ref": "EnrollmentTokens"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers",
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly"
|
||||
]
|
||||
},
|
||||
"create": {
|
||||
"description": "Creates a browser enrollment token in a domain.",
|
||||
"flatPath": "{customer}/chrome/enrollmentTokens",
|
||||
"httpMethod": "POST",
|
||||
"id": "cbcm.enrollmentTokens.create",
|
||||
"parameterOrder": [
|
||||
"customer"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/chrome/enrollmentTokens",
|
||||
"request": {
|
||||
"$ref": "CreateEnrollmentTokenRequest"
|
||||
},
|
||||
"response": {
|
||||
"$ref": "EnrollmentToken"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
},
|
||||
"revoke": {
|
||||
"description": "Revokes a browser enrollment token in a domain.",
|
||||
"flatPath": "{customer}/chrome/enrollmentTokens/{tokenPermanentId}:revoke",
|
||||
"httpMethod": "POST",
|
||||
"id": "cbcm.enrollmentTokens.revoke",
|
||||
"parameterOrder": [
|
||||
"customer",
|
||||
"tokenPermanentId"
|
||||
],
|
||||
"parameters": {
|
||||
"customer": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"tokenPermanentId": {
|
||||
"description": "Unique identifier for an enrollment token.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "{customer}/chrome/enrollmentTokens/{tokenPermanentId}:revoke",
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.directory.device.chromebrowsers"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"revision": "20201203",
|
||||
"rootUrl": "https://www.googleapis.com/admin/directory/v1.1beta1/customer/",
|
||||
"schemas": {
|
||||
"ChromeBrowser": {
|
||||
"id": "ChromeBrowser",
|
||||
"properties": {
|
||||
"annotatedAssetId": {
|
||||
"description": "Asset identifier as annotated by the administrator or specified during enrollment.",
|
||||
"type": "string"
|
||||
},
|
||||
"annotatedLocation": {
|
||||
"description": "Address or location of the device as annotated by the administrator.",
|
||||
"type": "string"
|
||||
},
|
||||
"annotatedNotes": {
|
||||
"description": "Notes about this device as annotated by the administrator",
|
||||
"type": "string"
|
||||
},
|
||||
"annotatedUser": {
|
||||
"description": "User of the device as annotated by the administrator.",
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"annotations": {
|
||||
"required": [
|
||||
"cbcm.chromebrowsers.update"
|
||||
]
|
||||
},
|
||||
"description": "The unique ID of the device.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"ChromeBrowsers": {
|
||||
"id": "ChromeBrowsers",
|
||||
"properties": {
|
||||
"browsers": {
|
||||
"description": "List of Chrome browser objects.",
|
||||
"items": {
|
||||
"$ref": "ChromeBrowser"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"etag": {
|
||||
"description": "ETag of the resource.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"default": "admin#directory#chromeosdevices",
|
||||
"description": "Kind of resource this is.",
|
||||
"type": "string"
|
||||
},
|
||||
"nextPageToken": {
|
||||
"description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"EnrollmentToken": {
|
||||
"id": "EnrollmentToken",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"default": "admin#directory#chromeEnrollmentToken",
|
||||
"description": "Kind of resource this is.",
|
||||
"type": "string"
|
||||
},
|
||||
"tokenId": {
|
||||
"description": "Enrollment Token ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"tokenPermanentId": {
|
||||
"description": "Enrollment Token Permanent ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"customerId": {
|
||||
"description": "Immutable ID of the G Suite account.",
|
||||
"type": "string"
|
||||
},
|
||||
"orgUnitPath": {
|
||||
"description": "The full path of the organizational unit or its unique ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"creatorId": {
|
||||
"description": "Creator ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"createTime": {
|
||||
"description": "Creation Time.",
|
||||
"type": "string"
|
||||
},
|
||||
"revokerId": {
|
||||
"description": "Revoker ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"revokeTime": {
|
||||
"description": "Revoke Time",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"EnrollmentTokens": {
|
||||
"id": "EnrollmentTokens",
|
||||
"properties": {
|
||||
"chrome_enrollment_tokens": {
|
||||
"description": "List of Chrome browser enrollment token objects.",
|
||||
"items": {
|
||||
"$ref": "EnrollmentToken"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"kind": {
|
||||
"default": "admin#directory#chromeEnrollmentTokens",
|
||||
"description": "Kind of resource this is.",
|
||||
"type": "string"
|
||||
},
|
||||
"nextPageToken": {
|
||||
"description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"CreateEnrollmentTokenRequest": {
|
||||
"id": "CreateEnrollmentTokenRequest",
|
||||
"properties": {
|
||||
"org_unit_path": {
|
||||
"description": "The full path of the organizational unit or its unique ID.",
|
||||
"type": "string"
|
||||
},
|
||||
"expire_time": {
|
||||
"description": "Expiration Time.",
|
||||
"type": "string"
|
||||
},
|
||||
"token_type": {
|
||||
"annotations": {
|
||||
"required": [
|
||||
"cbcm.enrollmentTokens.create"
|
||||
]
|
||||
},
|
||||
"description": "CHROME_BROWSER.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"MoveChromeBrowsersRequest": {
|
||||
"properties": {
|
||||
"org_unit_path": {
|
||||
"annotations": {
|
||||
"required": [
|
||||
"cbcm.chromebrowsers.moveChromeBrowsersToOu"
|
||||
]
|
||||
},
|
||||
"description": "Destination organization unit to move devices to. Full path of the organizational unit or its ID prefixed with id:",
|
||||
"type": "string"
|
||||
},
|
||||
"resource_ids": {
|
||||
"annotations": {
|
||||
"required": [
|
||||
"cbcm.chromebrowsers.moveChromeBrowsersToOu"
|
||||
]
|
||||
},
|
||||
"description": "List of unique device IDs of Chrome Browser Devices to move. A maximum of 600 browsers may be moved per request.",
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"servicePath": "",
|
||||
"title": "Admin SDK API",
|
||||
"version": "cbcm_v1.1beta1"
|
||||
}
|
||||
249
src/contactdelegation-v1.json
Normal file
249
src/contactdelegation-v1.json
Normal file
@@ -0,0 +1,249 @@
|
||||
{
|
||||
"auth": {
|
||||
"oauth2": {
|
||||
"scopes": {
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation": {
|
||||
"description": "View and manage your Contact Delegation"
|
||||
},
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation.readonly": {
|
||||
"description": "View your Contact Delegation"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"basePath": "",
|
||||
"baseUrl": "https://admin.googleapis.com/admin/contacts/v1/",
|
||||
"batchPath": "batch",
|
||||
"canonicalName": "contactdelegation",
|
||||
"description": "The Contact Delegation API allows Admins to delegate access of one user's, called the delegator, contacts to another user, called the delegate.",
|
||||
"discoveryVersion": "v1",
|
||||
"documentationLink": "https://developers.google.com/admin-sdk/contact-delegation",
|
||||
"fullyEncodeReservedExpansion": true,
|
||||
"icons": {
|
||||
"x16": "http://www.google.com/images/icons/product/search-16.gif",
|
||||
"x32": "http://www.google.com/images/icons/product/search-32.gif"
|
||||
},
|
||||
"id": "contactdelegation:v1",
|
||||
"kind": "discovery#restDescription",
|
||||
"name": "contactdelegation",
|
||||
"ownerDomain": "google.com",
|
||||
"ownerName": "Google",
|
||||
"packagePath": "admin",
|
||||
"parameters": {
|
||||
"$.xgafv": {
|
||||
"description": "V1 error format.",
|
||||
"enum": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"v1 error format",
|
||||
"v2 error format"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"access_token": {
|
||||
"description": "OAuth access token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"alt": {
|
||||
"default": "json",
|
||||
"description": "Data format for response.",
|
||||
"enum": [
|
||||
"json",
|
||||
"media",
|
||||
"proto"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"Responses with Content-Type of application/json",
|
||||
"Media download with context-dependent Content-Type",
|
||||
"Responses with Content-Type of application/x-protobuf"
|
||||
],
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"callback": {
|
||||
"description": "JSONP",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"description": "Selector specifying which fields to include in a partial response.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"oauth_token": {
|
||||
"description": "OAuth 2.0 token for the current user.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"prettyPrint": {
|
||||
"default": "true",
|
||||
"description": "Returns response with indentations and line breaks.",
|
||||
"location": "query",
|
||||
"type": "boolean"
|
||||
},
|
||||
"quotaUser": {
|
||||
"description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"uploadType": {
|
||||
"description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"upload_protocol": {
|
||||
"description": "Upload protocol for media (e.g. \"raw\", \"multipart\").",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"protocol": "rest",
|
||||
"resources": {
|
||||
"delegates": {
|
||||
"methods": {
|
||||
"create": {
|
||||
"description": "Creates a contact delegations",
|
||||
"flatPath": "users/{user}/delegates",
|
||||
"httpMethod": "POST",
|
||||
"id": "contactdelegations.delegates.create",
|
||||
"parameterOrder": [
|
||||
"user"
|
||||
],
|
||||
"parameters": {
|
||||
"user": {
|
||||
"description": "Email address of the delegator.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "users/{user}/delegates/{delegate}",
|
||||
"request": {
|
||||
"$ref": "Delegate"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation"
|
||||
]
|
||||
},
|
||||
"delete": {
|
||||
"description": "Deletes a contact delegation.",
|
||||
"flatPath": "users/{user}/delegates/{delegate}",
|
||||
"httpMethod": "DELETE",
|
||||
"id": "contactdelegations.delegates.delete",
|
||||
"parameterOrder": [
|
||||
"user",
|
||||
"delegate"
|
||||
],
|
||||
"parameters": {
|
||||
"delegate": {
|
||||
"description": "Email address of the delegate",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"description": "Email address of the delegator.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "users/{user}/delegates/{delegate}",
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation"
|
||||
]
|
||||
},
|
||||
"list": {
|
||||
"description": "Lists contact delegates for a user",
|
||||
"flatPath": "users/{user}/delegates",
|
||||
"httpMethod": "GET",
|
||||
"id": "contactdelegations.delegates.list",
|
||||
"parameterOrder": [
|
||||
"user"
|
||||
],
|
||||
"parameters": {
|
||||
"pageSize": {
|
||||
"description": "Determines how many delegates are returned in each response. ",
|
||||
"format": "int32",
|
||||
"location": "query",
|
||||
"minimum": "1",
|
||||
"type": "integer"
|
||||
},
|
||||
"pageToken": {
|
||||
"description": "Token to specify the next page in the list.",
|
||||
"location": "query",
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"description": "Email address of the delegator.",
|
||||
"location": "path",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"path": "users/{user}/delegates",
|
||||
"response": {
|
||||
"$ref": "Delegates"
|
||||
},
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation",
|
||||
"https://www.googleapis.com/auth/admin.contact.delegation.readonly"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"rootUrl": "https://admin.googleapis.com/admin/contacts/v1/",
|
||||
"schemas": {
|
||||
"Delegate": {
|
||||
"description": "JSON template for a delegate.",
|
||||
"id": "Delegate",
|
||||
"properties": {
|
||||
"email": {
|
||||
"description": "Email of the delegate.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Delegates": {
|
||||
"id": "Delegates",
|
||||
"properties": {
|
||||
"delegates": {
|
||||
"description": "List of delegates.",
|
||||
"items": {
|
||||
"$ref": "Delegate"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"etag": {
|
||||
"description": "ETag of the resource.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"default": "",
|
||||
"description": "Kind of resource this is.",
|
||||
"type": "string"
|
||||
},
|
||||
"nextPageToken": {
|
||||
"description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"servicePath": "",
|
||||
"title": "Contact Delegation API",
|
||||
"version": "v1",
|
||||
"version_module": true
|
||||
}
|
||||
@@ -140,7 +140,12 @@ case $gamos in
|
||||
echo_red "Sorry, you need to be running at least MacOS $gam_macos_ver to run GAM"
|
||||
exit
|
||||
fi
|
||||
gamfile="macos-x86_64-$use_macos_ver.tar.xz"
|
||||
gamfile="macos-x86_64.tar.xz"
|
||||
;;
|
||||
MINGW64_NT*)
|
||||
gamos="windows"
|
||||
echo "You are running Windows"
|
||||
gamfile="-windows-x86_64.zip"
|
||||
;;
|
||||
*)
|
||||
echo_red "Sorry, this installer currently only supports Linux and MacOS. Looks like you're runnning on $gamos. Exiting."
|
||||
@@ -154,8 +159,14 @@ else
|
||||
release_url="https://api.github.com/repos/jay0lee/GAM/releases/tags/v$gamversion"
|
||||
fi
|
||||
|
||||
echo_yellow "Checking GitHub URL $release_url for $gamversion GAM release..."
|
||||
release_json=$(curl -s $release_url 2>&1 /dev/null)
|
||||
if [ -z ${GHCLIENT+x} ]; then
|
||||
check_type="unauthenticated"
|
||||
else
|
||||
check_type="authenticated"
|
||||
fi
|
||||
|
||||
echo_yellow "Checking GitHub URL $release_url for $gamversion GAM release ($check_type)..."
|
||||
release_json=$(curl -s $GHCLIENT $release_url 2>&1 /dev/null)
|
||||
|
||||
echo_yellow "Getting file and download URL..."
|
||||
# Python is sadly the nearest to universal way to safely handle JSON with Bash
|
||||
@@ -222,14 +233,18 @@ temp_archive_dir=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir')
|
||||
# Clean up after ourselves even if we are killed with CTRL-C
|
||||
trap "rm -rf $temp_archive_dir" EXIT
|
||||
|
||||
echo_yellow "Downloading file $name from $browser_download_url to $temp_archive_dir."
|
||||
echo_yellow "Downloading file $name from $browser_download_url to $temp_archive_dir ($check_type)..."
|
||||
# Save archive to temp w/o losing our path
|
||||
(cd $temp_archive_dir && curl -O -L $browser_download_url)
|
||||
(cd $temp_archive_dir && curl -O -L $GHCLIENT $browser_download_url)
|
||||
|
||||
mkdir -p "$target_dir"
|
||||
|
||||
echo_yellow "Extracting archive to $target_dir"
|
||||
tar xf $temp_archive_dir/$name -C "$target_dir"
|
||||
if [[ "${name}" == *.tar.xz ]]; then
|
||||
tar xf $temp_archive_dir/$name -C "$target_dir"
|
||||
else
|
||||
unzip "${temp_archive_dir}/${name}" -d "${target_dir}"
|
||||
fi
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
echo_red "ERROR: extracting the GAM archive with tar failed with error $rc. Exiting."
|
||||
|
||||
46
src/gam-universal2.spec
Normal file
46
src/gam-universal2.spec
Normal file
@@ -0,0 +1,46 @@
|
||||
# -*- mode: python -*-
|
||||
|
||||
import sys
|
||||
|
||||
import importlib
|
||||
from PyInstaller.utils.hooks import copy_metadata
|
||||
|
||||
sys.modules['FixTk'] = None
|
||||
|
||||
# dynamically determine where httplib2/cacerts.txt lives
|
||||
proot = os.path.dirname(importlib.import_module('httplib2').__file__)
|
||||
extra_files = [(os.path.join(proot, 'cacerts.txt'), 'httplib2')]
|
||||
|
||||
extra_files += copy_metadata('google-api-python-client')
|
||||
extra_files += [('cbcm-v1.1beta1.json', '.')]
|
||||
|
||||
a = Analysis(['gam/__main__.py'],
|
||||
hiddenimports=[],
|
||||
hookspath=None,
|
||||
excludes=['FixTk', 'tcl', 'tk', '_tkinter', 'tkinter', 'Tkinter'],
|
||||
datas=extra_files,
|
||||
runtime_hooks=None)
|
||||
|
||||
for d in a.datas:
|
||||
if 'pyconfig' in d[0]:
|
||||
a.datas.remove(d)
|
||||
break
|
||||
|
||||
|
||||
pyz = PYZ(a.pure)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
name='gam',
|
||||
debug=False,
|
||||
strip=None,
|
||||
upx=False,
|
||||
console=True )
|
||||
|
||||
app = BUNDLE(exe,
|
||||
name='gam.app',
|
||||
icon=None,
|
||||
bundle_identifier=None,
|
||||
info_plist={'LSArchitecturePriority': 'arm64,x86_64'})
|
||||
@@ -12,6 +12,8 @@ proot = os.path.dirname(importlib.import_module('httplib2').__file__)
|
||||
extra_files = [(os.path.join(proot, 'cacerts.txt'), 'httplib2')]
|
||||
|
||||
extra_files += copy_metadata('google-api-python-client')
|
||||
extra_files += [('cbcm-v1.1beta1.json', '.')]
|
||||
extra_files += [('contactdelegation-v1.json', '.')]
|
||||
|
||||
a = Analysis(['gam/__main__.py'],
|
||||
hiddenimports=[],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Main behavioral methods and argument routing for GAM."""
|
||||
|
||||
|
||||
import base64
|
||||
import configparser
|
||||
import csv
|
||||
@@ -26,6 +27,7 @@ import webbrowser
|
||||
import zipfile
|
||||
import http.client as http_client
|
||||
from multiprocessing import Pool as mp_pool
|
||||
from multiprocessing import Lock as mp_lock
|
||||
from urllib.parse import quote, urlencode, urlparse
|
||||
import dateutil.parser
|
||||
|
||||
@@ -44,13 +46,18 @@ from cryptography.x509.oid import NameOID
|
||||
|
||||
import gam.auth.oauth
|
||||
from gam import auth
|
||||
from gam.auth import yubikey
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import fileutils
|
||||
from gam.gapi import calendar as gapi_calendar
|
||||
from gam.gapi import cloudidentity as gapi_cloudidentity
|
||||
from gam.gapi import cbcm as gapi_cbcm
|
||||
from gam.gapi import chromepolicy as gapi_chromepolicy
|
||||
from gam.gapi.cloudidentity import devices as gapi_cloudidentity_devices
|
||||
from gam.gapi.cloudidentity import groups as gapi_cloudidentity_groups
|
||||
from gam.gapi.cloudidentity import userinvitations as gapi_cloudidentity_userinvitations
|
||||
from gam.gapi import contactdelegation as gapi_contactdelegation
|
||||
from gam.gapi.directory import asps as gapi_directory_asps
|
||||
from gam.gapi.directory import cros as gapi_directory_cros
|
||||
from gam.gapi.directory import customer as gapi_directory_customer
|
||||
@@ -59,6 +66,7 @@ from gam.gapi.directory import domains as gapi_directory_domains
|
||||
from gam.gapi.directory import groups as gapi_directory_groups
|
||||
from gam.gapi.directory import mobiledevices as gapi_directory_mobiledevices
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
from gam.gapi.directory import printers as gapi_directory_printers
|
||||
from gam.gapi.directory import privileges as gapi_directory_privileges
|
||||
from gam.gapi.directory import resource as gapi_directory_resource
|
||||
from gam.gapi.directory import roles as gapi_directory_roles
|
||||
@@ -74,11 +82,7 @@ from gam import transport
|
||||
from gam import utils
|
||||
from gam.var import *
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
# No crypt module on Win, use passlib
|
||||
from passlib.hash import sha512_crypt
|
||||
else:
|
||||
from crypt import crypt
|
||||
from passlib.hash import sha512_crypt
|
||||
|
||||
if platform.system() == 'Linux':
|
||||
import distro
|
||||
@@ -397,8 +401,8 @@ def SetGlobalVariables():
|
||||
ROW_FILTER_COMP_PATTERN = re.compile(
|
||||
r'^(date|time|count)\s*([<>]=?|=|!=)\s*(.+)$', re.IGNORECASE)
|
||||
ROW_FILTER_BOOL_PATTERN = re.compile(r'^(boolean):(.+)$', re.IGNORECASE)
|
||||
ROW_FILTER_RE_PATTERN = re.compile(r'^(regex|notregex):(.+)$',
|
||||
re.IGNORECASE)
|
||||
ROW_FILTER_RE_PATTERN = re.compile(r'^(regex|notregex):(.+)$', re.IGNORECASE)
|
||||
REGEX_CHARS = '^$*+|$[{('
|
||||
|
||||
def _getCfgRowFilter(itemName):
|
||||
value = GC_Defaults[itemName]
|
||||
@@ -436,6 +440,17 @@ def SetGlobalVariables():
|
||||
)
|
||||
filterDict[column] = filterStr
|
||||
for column, filterStr in iter(filterDict.items()):
|
||||
for c in REGEX_CHARS:
|
||||
if c in column:
|
||||
columnPat = column
|
||||
break
|
||||
else:
|
||||
columnPat = f'^{column}$'
|
||||
try:
|
||||
columnPat = re.compile(columnPat, re.IGNORECASE)
|
||||
except re.error as e:
|
||||
controlflow.system_error_exit(
|
||||
3, f'Item: {itemName}: "{column}", Invalid RE: {str(e)}')
|
||||
mg = ROW_FILTER_COMP_PATTERN.match(filterStr)
|
||||
if mg:
|
||||
if mg.group(1) in ['date', 'time']:
|
||||
@@ -446,7 +461,7 @@ def SetGlobalVariables():
|
||||
valid, filterValue = utils.get_row_filter_time_or_delta_from_now(
|
||||
mg.group(3))
|
||||
if valid:
|
||||
rowFilters[column] = (mg.group(1), mg.group(2),
|
||||
rowFilters[column] = (columnPat, mg.group(1), mg.group(2),
|
||||
filterValue)
|
||||
continue
|
||||
controlflow.system_error_exit(
|
||||
@@ -455,7 +470,7 @@ def SetGlobalVariables():
|
||||
)
|
||||
else: #count
|
||||
if mg.group(3).isdigit():
|
||||
rowFilters[column] = (mg.group(1), mg.group(2),
|
||||
rowFilters[column] = (columnPat, mg.group(1), mg.group(2),
|
||||
int(mg.group(3)))
|
||||
continue
|
||||
controlflow.system_error_exit(
|
||||
@@ -474,12 +489,12 @@ def SetGlobalVariables():
|
||||
3,
|
||||
f'Item: {itemName}, Value: "{column}": "{filterStr}", Expected true|false'
|
||||
)
|
||||
rowFilters[column] = (mg.group(1), filterValue)
|
||||
rowFilters[column] = (columnPat, mg.group(1), filterValue)
|
||||
continue
|
||||
mg = ROW_FILTER_RE_PATTERN.match(filterStr)
|
||||
if mg:
|
||||
try:
|
||||
rowFilters[column] = (mg.group(1), re.compile(mg.group(2)))
|
||||
rowFilters[column] = (columnPat, mg.group(1), re.compile(mg.group(2)))
|
||||
continue
|
||||
except re.error as e:
|
||||
controlflow.system_error_exit(
|
||||
@@ -517,6 +532,7 @@ def SetGlobalVariables():
|
||||
_getOldEnvVar(GC_CSV_HEADER_FILTER, 'GAM_CSV_HEADER_FILTER')
|
||||
_getOldEnvVar(GC_CSV_HEADER_DROP_FILTER, 'GAM_CSV_HEADER_DROP_FILTER')
|
||||
_getOldEnvVar(GC_CSV_ROW_FILTER, 'GAM_CSV_ROW_FILTER')
|
||||
_getOldEnvVar(GC_CSV_ROW_DROP_FILTER, 'GAM_CSV_ROW_DROP_FILTER')
|
||||
_getOldEnvVar(GC_TLS_MIN_VERSION, 'GAM_TLS_MIN_VERSION')
|
||||
_getOldEnvVar(GC_TLS_MAX_VERSION, 'GAM_TLS_MAX_VERSION')
|
||||
_getOldEnvVar(GC_CA_FILE, 'GAM_CA_FILE')
|
||||
@@ -807,8 +823,14 @@ def _getSvcAcctData():
|
||||
def getSvcAcctCredentials(scopes, act_as):
|
||||
try:
|
||||
_getSvcAcctData()
|
||||
credentials = google.oauth2.service_account.Credentials.from_service_account_info(
|
||||
GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
|
||||
sign_method = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA].get('key_type', 'default')
|
||||
if sign_method == 'default':
|
||||
credentials = google.oauth2.service_account.Credentials.from_service_account_info(
|
||||
GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
|
||||
elif sign_method == 'yubikey':
|
||||
yksigner = yubikey.YubiKey(GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
|
||||
credentials = google.oauth2.service_account.Credentials._from_signer_and_info(yksigner,
|
||||
GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
|
||||
credentials = credentials.with_scopes(scopes)
|
||||
if act_as:
|
||||
credentials = credentials.with_subject(act_as)
|
||||
@@ -891,6 +913,7 @@ def getService(api, http):
|
||||
version,
|
||||
http=http,
|
||||
cache_discovery=False,
|
||||
static_discovery=False,
|
||||
discoveryServiceUrl=discoveryServiceUrl)
|
||||
GM_Globals[GM_CURRENT_API_SERVICES].setdefault(api, {})
|
||||
GM_Globals[GM_CURRENT_API_SERVICES][api][
|
||||
@@ -911,7 +934,7 @@ def getService(api, http):
|
||||
controlflow.wait_on_failure(n, retries, str(e))
|
||||
continue
|
||||
controlflow.system_error_exit(17, str(e))
|
||||
except (http_client.ResponseNotReady, socket.error,
|
||||
except (http_client.ResponseNotReady, OSError,
|
||||
googleapiclient.errors.HttpError) as e:
|
||||
if n != retries:
|
||||
controlflow.wait_on_failure(n, retries, str(e))
|
||||
@@ -1091,7 +1114,7 @@ def buildAlertCenterGAPIObject(user):
|
||||
|
||||
def buildActivityGAPIObject(user):
|
||||
userEmail = convertUIDtoEmailAddress(user)
|
||||
return (userEmail, buildGAPIServiceObject('appsactivity', userEmail))
|
||||
return (userEmail, buildGAPIServiceObject('driveactivity', userEmail))
|
||||
|
||||
|
||||
def buildDriveGAPIObject(user):
|
||||
@@ -1400,9 +1423,7 @@ def addDelegates(users, i):
|
||||
|
||||
|
||||
def gen_sha512_hash(password):
|
||||
if platform.system() == 'Windows':
|
||||
return sha512_crypt.hash(password, rounds=5000)
|
||||
return crypt(password)
|
||||
return sha512_crypt.hash(password, rounds=5000)
|
||||
|
||||
|
||||
def printShowDelegates(users, csvFormat):
|
||||
@@ -1423,7 +1444,7 @@ def printShowDelegates(users, csvFormat):
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam <users> show delegates')
|
||||
f"gam <users> {['show', 'print'][csvFormat]} delegates")
|
||||
count = len(users)
|
||||
i = 1
|
||||
for user in users:
|
||||
@@ -1718,7 +1739,7 @@ def doPrintAdmins():
|
||||
value = f'id:{value}'
|
||||
admin_attrib[
|
||||
'orgUnit'] = gapi_directory_orgunits.orgunit_from_orgunitid(
|
||||
value)
|
||||
value, cd)
|
||||
admin_attrib[key] = value
|
||||
csvRows.append(admin_attrib)
|
||||
display.write_csv_file(csvRows, titles, 'Admins', todrive)
|
||||
@@ -2419,7 +2440,7 @@ def doPrintCourses():
|
||||
if ownerEmails is not None:
|
||||
ownerId = course['ownerId']
|
||||
if ownerId not in ownerEmails:
|
||||
ownerEmails[ownerId] = convertUIDtoEmailAddress(f'uid{ownerId}',
|
||||
ownerEmails[ownerId] = convertUIDtoEmailAddress(f'uid:{ownerId}',
|
||||
cd=cd)
|
||||
course['ownerEmail'] = ownerEmails[ownerId]
|
||||
for field in skipFieldsList:
|
||||
@@ -2718,7 +2739,7 @@ def deletePhoto(users):
|
||||
for user in users:
|
||||
i += 1
|
||||
print(f'Deleting photo for {user}{currentCount(i, count)}')
|
||||
gapi.call(cd.users().photos(), 'delete', userKey=user)
|
||||
gapi.call(cd.users().photos(), 'delete', userKey=user, soft_errors=True)
|
||||
|
||||
|
||||
def printDriveSettings(users):
|
||||
@@ -2786,23 +2807,58 @@ def getTeamDriveThemes(users):
|
||||
|
||||
|
||||
def printDriveActivity(users):
|
||||
drive_ancestorId = 'root'
|
||||
drive_fileId = None
|
||||
def _get_user_info(user_id):
|
||||
if user_id.startswith('people/'):
|
||||
user_id = user_id[7:]
|
||||
entry = user_info.get(user_id)
|
||||
if entry is None:
|
||||
result = gapi.call(cd.users(), 'get',
|
||||
soft_errors=True,
|
||||
userKey=user_id, fields='primaryEmail,name.fullName')
|
||||
if result:
|
||||
entry = (result['primaryEmail'], result['name']['fullName'])
|
||||
else:
|
||||
entry = (f'uid:{user_id}', 'Unknown')
|
||||
user_info[user_id] = entry
|
||||
return entry
|
||||
|
||||
def _update_known_users(structure):
|
||||
if isinstance(structure, list):
|
||||
for v in structure:
|
||||
if isinstance(v, (dict, list)):
|
||||
_update_known_users(v)
|
||||
elif isinstance(structure, dict):
|
||||
for k, v in sorted(iter(structure.items())):
|
||||
if k != 'knownUser':
|
||||
if isinstance(v, (dict, list)):
|
||||
_update_known_users(v)
|
||||
else:
|
||||
entry = _get_user_info(v['personName'])
|
||||
v['emailAddress'] = entry[0]
|
||||
v['personName'] = entry[1]
|
||||
break
|
||||
|
||||
cd = buildGAPIObject('directory')
|
||||
drive_key = 'ancestorName'
|
||||
drive_fileId = 'root'
|
||||
user_info = {}
|
||||
todrive = False
|
||||
titles = [
|
||||
'user.name', 'user.permissionId', 'target.id', 'target.name',
|
||||
'target.mimeType'
|
||||
'user.name', 'user.emailAddress', 'target.id', 'target.name',
|
||||
'target.mimeType', 'eventTime'
|
||||
]
|
||||
sort_titles = titles[:]
|
||||
csvRows = []
|
||||
i = 5
|
||||
while i < len(sys.argv):
|
||||
activity_object = sys.argv[i].lower().replace('_', '')
|
||||
if activity_object == 'fileid':
|
||||
drive_fileId = sys.argv[i + 1]
|
||||
drive_ancestorId = None
|
||||
drive_key = 'itemName'
|
||||
i += 2
|
||||
elif activity_object == 'folderid':
|
||||
drive_ancestorId = sys.argv[i + 1]
|
||||
drive_fileId = sys.argv[i + 1]
|
||||
drive_key = 'ancestorName'
|
||||
i += 2
|
||||
elif activity_object == 'todrive':
|
||||
todrive = True
|
||||
@@ -2810,23 +2866,57 @@ def printDriveActivity(users):
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam <users> show driveactivity')
|
||||
|
||||
for user in users:
|
||||
user, activity = buildActivityGAPIObject(user)
|
||||
if not activity:
|
||||
continue
|
||||
page_token = None
|
||||
total_items = 0
|
||||
kwargs = {drive_key: f'items/{drive_fileId}',
|
||||
'pageToken': page_token}
|
||||
page_message = gapi.got_total_items_msg(f'Activities for {user}', '')
|
||||
feed = gapi.get_all_pages(activity.activities(),
|
||||
'list',
|
||||
'activities',
|
||||
page_message=page_message,
|
||||
source='drive.google.com',
|
||||
userId='me',
|
||||
drive_ancestorId=drive_ancestorId,
|
||||
groupingStrategy='none',
|
||||
drive_fileId=drive_fileId)
|
||||
for item in feed:
|
||||
display.add_row_titles_to_csv_file(
|
||||
utils.flatten_json(item['combinedEvent']), csvRows, titles)
|
||||
while True:
|
||||
feed = gapi.call(activity.activity(), 'query', body=kwargs)
|
||||
page_token, total_items = gapi.process_page(feed, 'activities', None, total_items, page_message, None)
|
||||
kwargs['pageToken'] = page_token
|
||||
if feed:
|
||||
for activity_event in feed.get('activities', []):
|
||||
event_row = {}
|
||||
actors = activity_event.get('actors', [])
|
||||
if actors:
|
||||
userId = actors[0].get('user', {}).get('knownUser', {}).get('personName', '')
|
||||
if not userId:
|
||||
userId = actors[0].get('impersonation', {}).get('impersonatedUser', {}).get('knownUser', {}).get('personName', '')
|
||||
if userId:
|
||||
entry = _get_user_info(userId)
|
||||
event_row['user.name'] = entry[1]
|
||||
event_row['user.emailAddress'] = entry[0]
|
||||
targets = activity_event.get('targets', [])
|
||||
if targets:
|
||||
driveItem = targets[0].get('driveItem')
|
||||
if driveItem:
|
||||
event_row['target.id'] = driveItem['name'][6:]
|
||||
event_row['target.name'] = driveItem['title']
|
||||
event_row['target.mimeType'] = driveItem['mimeType']
|
||||
else:
|
||||
teamDrive = targets[0].get('teamDrive')
|
||||
if teamDrive:
|
||||
event_row['target.id'] = teamDrive['name'][11:]
|
||||
event_row['target.name'] = teamDrive['title']
|
||||
if 'timestamp' in activity_event:
|
||||
event_row['eventTime'] = activity_event.pop('timestamp')
|
||||
elif 'timeRange' in activity_event:
|
||||
timeRange = activity_event.pop('timeRange')
|
||||
event_row['eventTime'] = f'{timeRange["startTime"]}-{timeRange["endTime"]}'
|
||||
_update_known_users(activity_event)
|
||||
display.add_row_titles_to_csv_file(
|
||||
utils.flatten_json(activity_event, flattened=event_row), csvRows, titles)
|
||||
del feed
|
||||
if not page_token:
|
||||
gapi.finalize_page_message(page_message)
|
||||
break
|
||||
display.sort_csv_titles(sort_titles, titles)
|
||||
display.write_csv_file(csvRows, titles, 'Drive Activity', todrive)
|
||||
|
||||
|
||||
@@ -3191,6 +3281,7 @@ def printDriveFileList(users):
|
||||
titles.append(attrib)
|
||||
a_file[attrib] = ' '.join(f_file[attrib])
|
||||
else:
|
||||
a_file[attrib] = len(f_file[attrib])
|
||||
for j, l_attrib in enumerate(f_file[attrib]):
|
||||
for list_attrib in l_attrib:
|
||||
if list_attrib in [
|
||||
@@ -3541,6 +3632,9 @@ def getDriveFileAttribute(i, body, parameters, myarg, update=False):
|
||||
elif myarg == 'writerscantshare':
|
||||
body['writersCanShare'] = False
|
||||
i += 1
|
||||
elif myarg == 'writerscanshare':
|
||||
body['writersCanShare'] = True
|
||||
i += 1
|
||||
elif myarg == 'contentrestrictions':
|
||||
body['contentRestrictions'] = [{}]
|
||||
restriction = sys.argv[i+1].lower().replace('_', '')
|
||||
@@ -3568,6 +3662,10 @@ def getDriveFileAttribute(i, body, parameters, myarg, update=False):
|
||||
return i
|
||||
|
||||
|
||||
def has_multiple_parents(body):
|
||||
return len(body.get('parents', [])) > 1
|
||||
|
||||
|
||||
def doUpdateDriveFile(users):
|
||||
fileIdSelection = {'fileIds': [], 'query': None}
|
||||
media_body = None
|
||||
@@ -3616,6 +3714,9 @@ def doUpdateDriveFile(users):
|
||||
body.setdefault('parents', [])
|
||||
for a_parent in more_parents:
|
||||
body['parents'].append({'id': a_parent})
|
||||
if has_multiple_parents(body):
|
||||
sys.stderr.write(f"Multiple parents ({len(body['parents'])}) specified for {user}, only one is allowed.\n")
|
||||
continue
|
||||
if fileIdSelection['query']:
|
||||
fileIdSelection['fileIds'] = doDriveSearch(
|
||||
drive, query=fileIdSelection['query'])
|
||||
@@ -3703,6 +3804,9 @@ def createDriveFile(users):
|
||||
body.setdefault('parents', [])
|
||||
for a_parent in more_parents:
|
||||
body['parents'].append({'id': a_parent})
|
||||
if has_multiple_parents(body):
|
||||
sys.stderr.write(f"Multiple parents ({len(body['parents'])}) specified for {user}, only one is allowed.\n")
|
||||
continue
|
||||
if parameters[DFA_LOCALFILEPATH]:
|
||||
media_body = googleapiclient.http.MediaFileUpload(
|
||||
parameters[DFA_LOCALFILEPATH],
|
||||
@@ -6208,7 +6312,7 @@ def doCreateOrUpdateUserSchema(updateCmd):
|
||||
if myarg == 'type':
|
||||
a_field['fieldType'] = sys.argv[i + 1].upper()
|
||||
validTypes = [
|
||||
'BOOL', 'DOUBLE', 'EMAIL', 'INT64', 'PHONE', 'STRING'
|
||||
'BOOL', 'DATE', 'DOUBLE', 'EMAIL', 'INT64', 'PHONE', 'STRING'
|
||||
]
|
||||
if a_field['fieldType'] not in validTypes:
|
||||
controlflow.expected_argument_exit(
|
||||
@@ -7592,32 +7696,14 @@ def _generatePrivateKeyAndPublicCert(client_id, key_size):
|
||||
return private_pem, publicKeyData
|
||||
|
||||
|
||||
def _formatOAuth2ServiceData(project_id, client_email, client_id, private_key,
|
||||
private_key_id):
|
||||
quoted_email = quote(client_email)
|
||||
key_json = {
|
||||
'auth_provider_x509_cert_url':
|
||||
'https://www.googleapis.com/oauth2/v1/certs',
|
||||
'auth_uri':
|
||||
'https://accounts.google.com/o/oauth2/auth',
|
||||
'client_email':
|
||||
client_email,
|
||||
'client_id':
|
||||
client_id,
|
||||
'client_x509_cert_url':
|
||||
f'https://www.googleapis.com/robot/v1/metadata/x509/{quoted_email}',
|
||||
'private_key':
|
||||
private_key,
|
||||
'private_key_id':
|
||||
private_key_id,
|
||||
'project_id':
|
||||
project_id,
|
||||
'token_uri':
|
||||
'https://oauth2.googleapis.com/token',
|
||||
'type':
|
||||
'service_account',
|
||||
}
|
||||
return json.dumps(key_json, indent=2, sort_keys=True)
|
||||
def _formatOAuth2ServiceData(service_data):
|
||||
quoted_email = quote(service_data.get('client_email', ''))
|
||||
service_data['auth_provider_x509_cert_url'] = 'https://www.googleapis.com/oauth2/v1/certs'
|
||||
service_data['auth_uri'] = 'https://accounts.google.com/o/oauth2/auth'
|
||||
service_data['client_x509_cert_url'] = f'https://www.googleapis.com/robot/v1/metadata/x509/{quoted_email}'
|
||||
service_data['token_uri'] = 'https://oauth2.googleapis.com/token'
|
||||
service_data['type'] = 'service_account'
|
||||
return json.dumps(service_data, indent=2, sort_keys=True)
|
||||
|
||||
|
||||
def doShowServiceAccountKeys():
|
||||
@@ -7663,10 +7749,22 @@ def doCreateOrRotateServiceAccountKeys(iam=None,
|
||||
client_email=None,
|
||||
client_id=None):
|
||||
local_key_size = 2048
|
||||
mode = 'retainexisting'
|
||||
body = {}
|
||||
if iam:
|
||||
mode = 'retainexisting'
|
||||
new_data = {
|
||||
'client_email': client_email,
|
||||
'project_id': project_id,
|
||||
'client_id': client_id,
|
||||
'key_type': 'default'
|
||||
}
|
||||
else:
|
||||
_getSvcAcctData()
|
||||
# dict() ensures we have a real copy, not pointer
|
||||
new_data = dict(GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
|
||||
oldPrivateKeyId = new_data.get('private_key_id')
|
||||
# assume default key type unless we are told otherwise
|
||||
new_data['key_type'] = 'default'
|
||||
mode = 'retainnone'
|
||||
i = 3
|
||||
iam = buildGAPIServiceObject('iam', None)
|
||||
@@ -7691,38 +7789,64 @@ def doCreateOrRotateServiceAccountKeys(iam=None,
|
||||
'localkeysize must be 1024, 2048 or 4096. 1024 is weak and dangerous. 2048 is recommended. 4096 is slow.'
|
||||
)
|
||||
i += 2
|
||||
elif myarg == 'yubikey':
|
||||
new_data['key_type'] = 'yubikey'
|
||||
i += 1
|
||||
elif myarg == 'yubikeyslot':
|
||||
new_data['yubikey_slot'] = sys.argv[i+1].upper()
|
||||
i =+ 2
|
||||
elif myarg == 'yubikeypin':
|
||||
new_data['yubikey_pin'] = input('Enter your YubiKey PIN: ')
|
||||
i += 1
|
||||
elif myarg == 'yubikeyserialnumber':
|
||||
try:
|
||||
new_data['yubikey_serial_number'] = int(sys.argv[i+1])
|
||||
except ValueError:
|
||||
controlflow.system_error_exit(
|
||||
3,
|
||||
'yubikey_serial_number must be a number')
|
||||
i += 2
|
||||
elif myarg in ['retainnone', 'retainexisting', 'replacecurrent']:
|
||||
mode = myarg
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg, 'gam rotate sakeys')
|
||||
currentPrivateKeyId = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA][
|
||||
'private_key_id']
|
||||
project_id = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]['project_id']
|
||||
client_email = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]['client_email']
|
||||
client_id = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]['client_id']
|
||||
clientId = GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID]
|
||||
name = f'projects/-/serviceAccounts/{clientId}'
|
||||
if mode != 'retainexisting':
|
||||
keys = gapi.get_items(iam.projects().serviceAccounts().keys(),
|
||||
'list',
|
||||
'keys',
|
||||
name=name,
|
||||
keyTypes='USER_MANAGED')
|
||||
sa_name = f'projects/-/serviceAccounts/{new_data["client_id"]}'
|
||||
if new_data.get('key_type') == 'yubikey':
|
||||
# Use yubikey private key
|
||||
new_data['yubikey_key_type'] = f'RSA{local_key_size}'
|
||||
new_data.pop('private_key', None)
|
||||
yk = yubikey.YubiKey(new_data)
|
||||
publicKeyData = yk.get_certificate()
|
||||
elif local_key_size:
|
||||
# Generate private key locally, store in file
|
||||
new_data['private_key'], publicKeyData = _generatePrivateKeyAndPublicCert(
|
||||
sa_name, local_key_size)
|
||||
new_data['key_type'] = 'default'
|
||||
for key in list(new_data):
|
||||
if key.startswith('yubikey_'):
|
||||
new_data.pop(key, None)
|
||||
if local_key_size:
|
||||
private_key, publicKeyData = _generatePrivateKeyAndPublicCert(
|
||||
name, local_key_size)
|
||||
# Upload public cert for yubikey or local generated
|
||||
print(' Uploading new public certificate to Google...')
|
||||
throw_reasons = [
|
||||
gapi_errors.ErrorReason.FOUR_O_O,
|
||||
gapi_errors.ErrorReason.NOT_FOUND
|
||||
]
|
||||
max_retries = 10
|
||||
for i in range(1, max_retries + 1):
|
||||
try:
|
||||
result = gapi.call(
|
||||
iam.projects().serviceAccounts().keys(),
|
||||
'upload',
|
||||
throw_reasons=[gapi_errors.ErrorReason.NOT_FOUND],
|
||||
name=name,
|
||||
throw_reasons=throw_reasons,
|
||||
name=sa_name,
|
||||
body={'publicKeyData': publicKeyData})
|
||||
break
|
||||
except googleapiclient.errors.HttpError:
|
||||
print('WARNING: that key already exists.')
|
||||
result = {'name': oldPrivateKeyId}
|
||||
break
|
||||
except gapi_errors.GapiNotFoundError as e:
|
||||
if i == max_retries:
|
||||
raise e
|
||||
@@ -7732,31 +7856,36 @@ def doCreateOrRotateServiceAccountKeys(iam=None,
|
||||
f'Waiting for Service Account creation to complete. Sleeping {sleep_time} seconds\n'
|
||||
)
|
||||
time.sleep(sleep_time)
|
||||
private_key_id = result['name'].rsplit('/', 1)[-1]
|
||||
oauth2service_data = _formatOAuth2ServiceData(project_id, client_email,
|
||||
client_id, private_key,
|
||||
private_key_id)
|
||||
newPrivateKeyId = result['name'].rsplit('/', 1)[-1]
|
||||
new_data['private_key_id'] = newPrivateKeyId
|
||||
new_data_str = _formatOAuth2ServiceData(new_data)
|
||||
else:
|
||||
# Ask Google to generate private key, store locally
|
||||
result = gapi.call(iam.projects().serviceAccounts().keys(),
|
||||
'create',
|
||||
name=name,
|
||||
name=sa_name,
|
||||
body=body)
|
||||
oauth2service_data = base64.b64decode(
|
||||
new_data_str = base64.b64decode(
|
||||
result['privateKeyData']).decode(UTF8)
|
||||
private_key_id = result['name'].rsplit('/', 1)[-1]
|
||||
newPrivateKeyId = result['name'].rsplit('/', 1)[-1]
|
||||
fileutils.write_file(GC_Values[GC_OAUTH2SERVICE_JSON],
|
||||
oauth2service_data,
|
||||
new_data_str,
|
||||
continue_on_error=False)
|
||||
print(
|
||||
f' Wrote new private key {private_key_id} to {GC_Values[GC_OAUTH2SERVICE_JSON]}'
|
||||
f' Wrote new service account data for {newPrivateKeyId} to {GC_Values[GC_OAUTH2SERVICE_JSON]}'
|
||||
)
|
||||
if mode != 'retainexisting':
|
||||
keys = gapi.get_items(iam.projects().serviceAccounts().keys(),
|
||||
'list',
|
||||
'keys',
|
||||
name=sa_name,
|
||||
keyTypes='USER_MANAGED')
|
||||
count = len(keys) if mode == 'retainnone' else 1
|
||||
print(
|
||||
f' Revoking {count} existing key(s) for Service Account {clientId}')
|
||||
f' Revoking {count} existing key(s) for Service Account {new_data["client_id"]}')
|
||||
for key in keys:
|
||||
keyName = key['name'].rsplit('/', 1)[-1]
|
||||
if mode == 'retainnone' or keyName == currentPrivateKeyId:
|
||||
if (mode == 'retainnone' or keyName == oldPrivateKeyId) and keyName != newPrivateKeyId:
|
||||
print(f' Revoking existing key {keyName} for service account')
|
||||
gapi.call(iam.projects().serviceAccounts().keys(),
|
||||
'delete',
|
||||
@@ -7842,7 +7971,7 @@ def doPrintShowProjects(csvFormat):
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(
|
||||
myarg, f"gam <users> {['show', 'print'][csvFormat]} projects")
|
||||
myarg, f"gam {['show', 'print'][csvFormat]} projects")
|
||||
if not csvFormat:
|
||||
count = len(projects)
|
||||
print(f'User: {login_hint}, Show {count} Projects')
|
||||
@@ -8002,7 +8131,7 @@ def printShowTeamDrives(users, csvFormat):
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(
|
||||
myarg, 'gam <users> print|show teamdrives')
|
||||
myarg, f"gam {['show', 'print'][csvFormat]} teamdrives")
|
||||
tds = []
|
||||
for user in users:
|
||||
sys.stderr.write(f'Getting Team Drives for {user}\n')
|
||||
@@ -9663,11 +9792,13 @@ def getUsersToModify(entity_type=None,
|
||||
entity_type = sys.argv[1].lower()
|
||||
if entity is None:
|
||||
entity = sys.argv[2]
|
||||
cd = buildGAPIObject('directory')
|
||||
# avoid building cd for user/users since it
|
||||
# unnnecesarily pushes user through admin auth
|
||||
if entity_type not in ['user', 'users'] or \
|
||||
('@' not in entity and not GC_Values[GC_DOMAIN]):
|
||||
cd = buildGAPIObject('directory')
|
||||
if entity_type == 'user':
|
||||
users = [
|
||||
entity,
|
||||
]
|
||||
users = [entity]
|
||||
elif entity_type == 'users':
|
||||
users = entity.replace(',', ' ').split()
|
||||
elif entity_type in ['group', 'group_ns', 'group_susp', 'group_inde']:
|
||||
@@ -10112,6 +10243,16 @@ def doRequestOAuth(login_hint=None, scopes=None):
|
||||
|
||||
|
||||
OAUTH2_SCOPES = [
|
||||
{
|
||||
'name': 'Chrome Browser Cloud Management',
|
||||
'subscopes': ['readonly'],
|
||||
'scopes': 'https://www.googleapis.com/auth/admin.directory.device.chromebrowsers',
|
||||
},
|
||||
{
|
||||
'name': 'Chrome Policy API',
|
||||
'subscope': ['readonly'],
|
||||
'scopes': ['https://www.googleapis.com/auth/chrome.management.policy'],
|
||||
},
|
||||
{
|
||||
'name':
|
||||
'Classroom API - counts as 5 scopes',
|
||||
@@ -10129,14 +10270,23 @@ OAUTH2_SCOPES = [
|
||||
'subscopes': ['readonly'],
|
||||
'scopes': 'https://www.googleapis.com/auth/cloud-identity.groups'
|
||||
},
|
||||
{
|
||||
'name': 'Cloud Identity - User Invitations',
|
||||
'subscopes': ['readonly'],
|
||||
'scopes': 'https://www.googleapis.com/auth/cloud-identity.userinvitations'
|
||||
},
|
||||
{
|
||||
'name': 'Contact Delegation',
|
||||
'subscopes': ['readonly'],
|
||||
'scopes': 'https://www.googleapis.com/auth/admin.contact.delegation'
|
||||
},
|
||||
{
|
||||
'name': 'Data Transfer API',
|
||||
'subscopes': ['readonly'],
|
||||
'scopes': 'https://www.googleapis.com/auth/admin.datatransfer'
|
||||
},
|
||||
{
|
||||
'name':
|
||||
'Directory API - Chrome OS Devices',
|
||||
'name': 'Directory API - Chrome OS Devices',
|
||||
'subscopes': ['readonly'],
|
||||
'scopes':
|
||||
'https://www.googleapis.com/auth/admin.directory.device.chromeos'
|
||||
@@ -10157,12 +10307,17 @@ OAUTH2_SCOPES = [
|
||||
'scopes': 'https://www.googleapis.com/auth/admin.directory.group'
|
||||
},
|
||||
{
|
||||
'name':
|
||||
'Directory API - Mobile Devices',
|
||||
'name': 'Directory API - Mobile Devices',
|
||||
'subscopes': ['readonly', 'action'],
|
||||
'scopes':
|
||||
'https://www.googleapis.com/auth/admin.directory.device.mobile'
|
||||
},
|
||||
{
|
||||
'name': 'Directory API - Printers',
|
||||
'subscopes': ['readonly'],
|
||||
# note - currently DASA only but admin credentials should work soon
|
||||
'scopes': 'https://www.googleapis.com/auth/admin.chrome.printers'
|
||||
},
|
||||
{
|
||||
'name': 'Directory API - Organizational Units',
|
||||
'subscopes': ['readonly'],
|
||||
@@ -10744,7 +10899,9 @@ Append an 'r' to grant read-only access or an 'a' to grant action-only access.
|
||||
)
|
||||
|
||||
|
||||
def init_gam_worker():
|
||||
def init_gam_worker(l):
|
||||
global mplock
|
||||
mplock = l
|
||||
signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
|
||||
|
||||
@@ -10752,7 +10909,8 @@ def run_batch(items):
|
||||
if not items:
|
||||
return
|
||||
num_worker_threads = min(len(items), GC_Values[GC_NUM_THREADS])
|
||||
pool = mp_pool(num_worker_threads, init_gam_worker, maxtasksperchild=200)
|
||||
l = mp_lock()
|
||||
pool = mp_pool(num_worker_threads, init_gam_worker, maxtasksperchild=200, initargs=(l,))
|
||||
sys.stderr.write(f'Using {num_worker_threads} processes...\n')
|
||||
try:
|
||||
results = []
|
||||
@@ -10763,7 +10921,7 @@ def run_batch(items):
|
||||
)
|
||||
pool.close()
|
||||
pool.join()
|
||||
pool = mp_pool(num_worker_threads, init_gam_worker)
|
||||
pool = mp_pool(num_worker_threads, init_gam_worker, maxtasksperchild=200, initargs=(1,))
|
||||
sys.stderr.write(
|
||||
'commit-batch - running processes finished, proceeding\n')
|
||||
continue
|
||||
@@ -10876,10 +11034,13 @@ def ProcessGAMCommand(args):
|
||||
try:
|
||||
SetGlobalVariables()
|
||||
if sys.version_info[1] >= 7:
|
||||
sys.stdout.reconfigure(encoding=GC_Values[GC_CHARSET],
|
||||
errors='backslashreplace')
|
||||
sys.stdin.reconfigure(encoding=GC_Values[GC_CHARSET],
|
||||
errors='backslashreplace')
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding=GC_Values[GC_CHARSET],
|
||||
errors='backslashreplace')
|
||||
sys.stdin.reconfigure(encoding=GC_Values[GC_CHARSET],
|
||||
errors='backslashreplace')
|
||||
except AttributeError:
|
||||
pass
|
||||
command = sys.argv[1].lower()
|
||||
if command == 'batch':
|
||||
i = 2
|
||||
@@ -11023,6 +11184,10 @@ def ProcessGAMCommand(args):
|
||||
createGCPFolder()
|
||||
elif argument in ['adminrole']:
|
||||
gapi_directory_roles.create()
|
||||
elif argument in ['browsertoken', 'browsertokens']:
|
||||
gapi_cbcm.createtoken()
|
||||
elif argument in ['printer']:
|
||||
gapi_directory_printers.create()
|
||||
else:
|
||||
controlflow.invalid_argument_exit(argument, 'gam create')
|
||||
sys.exit(0)
|
||||
@@ -11077,6 +11242,14 @@ def ProcessGAMCommand(args):
|
||||
gapi_directory_resource.updateFeature()
|
||||
elif argument in ['adminrole']:
|
||||
gapi_directory_roles.update()
|
||||
elif argument == 'deviceuserstate':
|
||||
gapi_cloudidentity_devices.update_state()
|
||||
elif argument in ['browser', 'browsers']:
|
||||
gapi_cbcm.update()
|
||||
elif argument == 'chromepolicy':
|
||||
gapi_chromepolicy.update_policy()
|
||||
elif argument in ['printer']:
|
||||
gapi_directory_printers.update()
|
||||
else:
|
||||
controlflow.invalid_argument_exit(argument, 'gam update')
|
||||
sys.exit(0)
|
||||
@@ -11120,6 +11293,8 @@ def ProcessGAMCommand(args):
|
||||
gapi_directory_domainaliases.info()
|
||||
elif argument in ['resoldcustomer', 'resellercustomer']:
|
||||
doGetResoldCustomer()
|
||||
elif argument in ['printer']:
|
||||
gapi_directory_printers.info()
|
||||
elif argument in [
|
||||
'resoldsubscription', 'resoldsubscriptions',
|
||||
'resellersubscription', 'resellersubscriptions'
|
||||
@@ -11135,6 +11310,12 @@ def ProcessGAMCommand(args):
|
||||
gapi_directory_resource.getBuildingInfo()
|
||||
elif argument in ['device']:
|
||||
gapi_cloudidentity_devices.info()
|
||||
elif argument == 'deviceuserstate':
|
||||
gapi_cloudidentity_devices.info_state()
|
||||
elif argument in ['browser', 'browsers']:
|
||||
gapi_cbcm.info()
|
||||
elif argument in ['userinvitation', 'userinvitations']:
|
||||
gapi_cloudidentity_userinvitations.get()
|
||||
else:
|
||||
controlflow.invalid_argument_exit(argument, 'gam info')
|
||||
sys.exit(0)
|
||||
@@ -11142,6 +11323,8 @@ def ProcessGAMCommand(args):
|
||||
argument = sys.argv[2].lower()
|
||||
if argument in ['guardianinvitation', 'guardianinvitations']:
|
||||
doCancelGuardianInvitation()
|
||||
elif argument in ['userinvitation', 'userinvitations']:
|
||||
gapi_cloudidentity_userinvitations.cancel()
|
||||
else:
|
||||
controlflow.invalid_argument_exit(argument, 'gam cancel')
|
||||
sys.exit(0)
|
||||
@@ -11197,6 +11380,12 @@ def ProcessGAMCommand(args):
|
||||
doDeleteServiceAccountKeys()
|
||||
elif argument in ['adminrole']:
|
||||
gapi_directory_roles.delete()
|
||||
elif argument in ['browser', 'browsers']:
|
||||
gapi_cbcm.delete()
|
||||
elif argument in ['printer']:
|
||||
gapi_directory_printers.delete()
|
||||
elif argument == 'chromepolicy':
|
||||
gapi_chromepolicy.delete_policy()
|
||||
else:
|
||||
controlflow.invalid_argument_exit(argument, 'gam delete')
|
||||
sys.exit(0)
|
||||
@@ -11211,6 +11400,11 @@ def ProcessGAMCommand(args):
|
||||
else:
|
||||
controlflow.invalid_argument_exit(argument, 'gam undelete')
|
||||
sys.exit(0)
|
||||
elif command == 'revoke':
|
||||
argument = sys.argv[2].lower()
|
||||
if argument in ['browsertoken', 'browserokens']:
|
||||
gapi_cbcm.revoketoken()
|
||||
sys.exit(0)
|
||||
elif command in ['close', 'reopen']:
|
||||
# close and reopen will have to be split apart if either takes a new argument
|
||||
argument = sys.argv[2].lower()
|
||||
@@ -11287,9 +11481,28 @@ def ProcessGAMCommand(args):
|
||||
doPrintShowAlerts()
|
||||
elif argument in ['alertfeedback', 'alertsfeedback']:
|
||||
doPrintShowAlertFeedback()
|
||||
elif argument in ['browser', 'browsers']:
|
||||
gapi_cbcm.print_()
|
||||
elif argument in ['browsertoken', 'browsertokens']:
|
||||
gapi_cbcm.printshowtokens(True)
|
||||
elif argument in ['vaultcount']:
|
||||
gapi_vault.print_count()
|
||||
elif argument in ['userinvitations']:
|
||||
gapi_cloudidentity_userinvitations.print_()
|
||||
elif argument in ['printermodels']:
|
||||
gapi_directory_printers.print_models()
|
||||
elif argument in ['printers']:
|
||||
gapi_directory_printers.print_()
|
||||
else:
|
||||
controlflow.invalid_argument_exit(argument, 'gam print')
|
||||
sys.exit(0)
|
||||
elif command == 'send':
|
||||
argument = sys.argv[2].lower()
|
||||
if argument in ['userinvitation', 'userinvitations']:
|
||||
gapi_cloudidentity_userinvitations.send()
|
||||
else:
|
||||
controlflow.invalid_argument_exit(argument, 'gam send')
|
||||
sys.exit(0)
|
||||
elif command == 'show':
|
||||
argument = sys.argv[2].lower()
|
||||
if argument in ['schema', 'schemas']:
|
||||
@@ -11302,9 +11515,20 @@ def ProcessGAMCommand(args):
|
||||
doPrintShowProjects(False)
|
||||
elif argument in ['sakey', 'sakeys']:
|
||||
doShowServiceAccountKeys()
|
||||
elif argument in ['browsertoken', 'browsertokens']:
|
||||
gapi_cbcm.printshowtokens(False)
|
||||
elif argument in ['chromeschema', 'chromeschemas']:
|
||||
gapi_chromepolicy.printshow_schemas()
|
||||
elif argument in ['chromepolicy', 'chromepolicies']:
|
||||
gapi_chromepolicy.printshow_policies()
|
||||
else:
|
||||
controlflow.invalid_argument_exit(argument, 'gam show')
|
||||
sys.exit(0)
|
||||
elif command == 'move':
|
||||
argument = sys.argv[2].lower()
|
||||
if argument in ['browser', 'browsers']:
|
||||
gapi_cbcm.move()
|
||||
sys.exit(0)
|
||||
elif command in ['oauth', 'oauth2']:
|
||||
argument = sys.argv[2].lower()
|
||||
if argument in ['request', 'create']:
|
||||
@@ -11387,6 +11611,11 @@ def ProcessGAMCommand(args):
|
||||
else:
|
||||
controlflow.invalid_argument_exit(argument, 'gam rotate')
|
||||
sys.exit(0)
|
||||
elif command == 'check':
|
||||
argument = sys.argv[2].lower()
|
||||
if argument in ['isinvitable', 'userinvitation', 'userinvitations']:
|
||||
gapi_cloudidentity_userinvitations.is_invitable_user()
|
||||
sys.exit(0)
|
||||
elif command in ['cancelwipe', 'wipe', 'approve', 'block', 'sync']:
|
||||
target = sys.argv[2].lower().replace('_', '')
|
||||
if target in ['device', 'devices']:
|
||||
@@ -11492,6 +11721,8 @@ def ProcessGAMCommand(args):
|
||||
printShowTeamDrives(users, False)
|
||||
elif showWhat in ['teamdriveinfo']:
|
||||
doGetTeamDriveInfo(users)
|
||||
elif showWhat in ['contactdelegate', 'contactdelegates']:
|
||||
gapi_contactdelegation.print_(users, False)
|
||||
else:
|
||||
controlflow.invalid_argument_exit(showWhat, 'gam <users> show')
|
||||
elif command == 'print':
|
||||
@@ -11520,6 +11751,8 @@ def ProcessGAMCommand(args):
|
||||
printShowTokens(5, 'users', users, True)
|
||||
elif printWhat in ['teamdrive', 'teamdrives']:
|
||||
printShowTeamDrives(users, True)
|
||||
elif printWhat in ['contactdelegate', 'contactdelegates']:
|
||||
gapi_contactdelegation.print_(users, True)
|
||||
else:
|
||||
controlflow.invalid_argument_exit(printWhat,
|
||||
'gam <users> print')
|
||||
@@ -11600,6 +11833,8 @@ def ProcessGAMCommand(args):
|
||||
deleteSmime(users)
|
||||
elif delWhat == 'teamdrive':
|
||||
doDeleteTeamDrive(users)
|
||||
elif delWhat == 'contactdelegate':
|
||||
gapi_contactdelegation.delete(users)
|
||||
else:
|
||||
controlflow.invalid_argument_exit(delWhat, 'gam <users> delete')
|
||||
elif command in ['add', 'create']:
|
||||
@@ -11632,6 +11867,8 @@ def ProcessGAMCommand(args):
|
||||
addSmime(users)
|
||||
elif addWhat == 'teamdrive':
|
||||
doCreateTeamDrive(users)
|
||||
elif addWhat == 'contactdelegate':
|
||||
gapi_contactdelegation.create(users)
|
||||
else:
|
||||
controlflow.invalid_argument_exit(addWhat,
|
||||
f'gam <users> {command}')
|
||||
@@ -11708,6 +11945,8 @@ def ProcessGAMCommand(args):
|
||||
checkWhat = sys.argv[4].replace('_', '').lower()
|
||||
if checkWhat == 'serviceaccount':
|
||||
doCheckServiceAccount(users)
|
||||
elif checkWhat == 'isinvitable':
|
||||
gapi_cloudidentity_userinvitations.bulk_is_invitable(users)
|
||||
else:
|
||||
controlflow.invalid_argument_exit(checkWhat,
|
||||
'gam <users> check')
|
||||
@@ -11756,6 +11995,8 @@ def ProcessGAMCommand(args):
|
||||
gapi_directory_users.signout(users)
|
||||
elif command == 'turnoff2sv':
|
||||
gapi_directory_users.turn_off_2sv(users)
|
||||
elif command == 'waitformailbox':
|
||||
gapi_directory_users.wait_for_mailbox(users)
|
||||
else:
|
||||
controlflow.invalid_argument_exit(command, 'gam')
|
||||
except IndexError:
|
||||
@@ -11763,7 +12004,7 @@ def ProcessGAMCommand(args):
|
||||
sys.exit(2)
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(50)
|
||||
except socket.error as e:
|
||||
except OSError as e:
|
||||
controlflow.system_error_exit(3, str(e))
|
||||
except MemoryError:
|
||||
controlflow.system_error_exit(99, MESSAGE_GAM_OUT_OF_MEMORY)
|
||||
|
||||
@@ -5,7 +5,9 @@ import os
|
||||
|
||||
from google.auth.jwt import Credentials as JWTCredentials
|
||||
|
||||
import gam
|
||||
from gam.auth import oauth
|
||||
from gam.auth import yubikey
|
||||
from gam.var import _FN_OAUTH2_TXT
|
||||
from gam.var import _FN_OAUTH2SERVICE_JSON
|
||||
from gam.var import GC_OAUTH2_TXT
|
||||
@@ -36,10 +38,17 @@ def get_admin_credentials(api=None):
|
||||
with open(credential_file, 'r') as f:
|
||||
creds_data = json.load(f)
|
||||
# Validate that enable DASA matches content of authorization file
|
||||
if GC_Values[GC_ENABLE_DASA] and 'private_key' in creds_data:
|
||||
if GC_Values[GC_ENABLE_DASA] and 'private_key_id' in creds_data:
|
||||
audience = f'https://{api}.googleapis.com/'
|
||||
return JWTCredentials.from_service_account_info(creds_data,
|
||||
audience=audience)
|
||||
key_type = creds_data.get('key_type', 'default')
|
||||
if key_type == 'default':
|
||||
return JWTCredentials.from_service_account_info(creds_data,
|
||||
audience=audience)
|
||||
elif key_type == 'yubikey':
|
||||
yksigner = yubikey.YubiKey(creds_data)
|
||||
return JWTCredentials._from_signer_and_info(yksigner,
|
||||
creds_data,
|
||||
audience=audience)
|
||||
elif not GC_Values[GC_ENABLE_DASA] and 'token' in creds_data:
|
||||
return oauth.Credentials.from_credentials_file(credential_file)
|
||||
else:
|
||||
|
||||
74
src/gam/auth/yubikey.py
Normal file
74
src/gam/auth/yubikey.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from base64 import b64encode
|
||||
import sys
|
||||
from threading import Timer
|
||||
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from ykman.device import connect_to_device
|
||||
from yubikit.piv import KEY_TYPE, SLOT, InvalidPinError, PivSession
|
||||
from yubikit.core.smartcard import ApduError
|
||||
from gam import controlflow
|
||||
|
||||
class YubiKey():
|
||||
|
||||
def __init__(self, service_account_info):
|
||||
key_type = service_account_info.get('yubikey_key_type', 'RSA2048')
|
||||
try:
|
||||
self.key_type = getattr(KEY_TYPE, key_type.upper())
|
||||
except AttributeError:
|
||||
controlflow.system_error_exit(6, f'{key_type} is not a valid value for yubikey_key_type')
|
||||
slot = service_account_info.get('yubikey_slot', 'AUTHENTICATION')
|
||||
try:
|
||||
self.slot = getattr(SLOT, slot.upper())
|
||||
except AttributeError:
|
||||
controlflow.system_error_exit(6, f'{slot} is not a valid value for yubikey_slot')
|
||||
self.serial_number = service_account_info.get('yubikey_serial_number')
|
||||
self.pin = service_account_info.get('yubikey_pin')
|
||||
self.key_id = service_account_info.get('private_key_id')
|
||||
|
||||
def get_certificate(self):
|
||||
try:
|
||||
conn, _, _ = connect_to_device(self.serial_number)
|
||||
session = PivSession(conn)
|
||||
if self.pin:
|
||||
try:
|
||||
session.verify_pin(self.pin)
|
||||
except InvalidPinError as err:
|
||||
controlflow.system_error_exit(7, f'YubiKey - {err}')
|
||||
try:
|
||||
cert = session.get_certificate(self.slot)
|
||||
cert_pem = cert.public_bytes(
|
||||
serialization.Encoding.PEM).decode()
|
||||
publicKeyData = b64encode(cert_pem.encode())
|
||||
if isinstance(publicKeyData, bytes):
|
||||
publicKeyData = publicKeyData.decode()
|
||||
return publicKeyData
|
||||
except ApduError as err:
|
||||
controlflow.system_error_exit(8, f'YubiKey - {err}')
|
||||
except ValueError as err:
|
||||
controlflow.system_error_exit(9, f'YubiKey - {err}')
|
||||
|
||||
def sign(self, message):
|
||||
if 'mplock' in globals():
|
||||
mplock.acquire()
|
||||
try:
|
||||
conn, _, _ = connect_to_device(self.serial_number)
|
||||
session = PivSession(conn)
|
||||
if self.pin:
|
||||
try:
|
||||
session.verify_pin(self.pin)
|
||||
except InvalidPinError as err:
|
||||
controlflow.system_error_exit(7, f'YubiKey - {err}')
|
||||
try:
|
||||
signed = session.sign(slot=self.slot,
|
||||
key_type=self.key_type,
|
||||
message=message,
|
||||
hash_algorithm=hashes.SHA256(),
|
||||
padding=padding.PKCS1v15())
|
||||
except ApduError as err:
|
||||
controlflow.system_error_exit(8, f'YubiKey = {err}')
|
||||
except ValueError as err:
|
||||
controlflow.system_error_exit(9, f'YubiKey - {err}')
|
||||
if 'mplock' in globals():
|
||||
mplock.release()
|
||||
return signed
|
||||
@@ -66,7 +66,7 @@ def csv_field_error_exit(field_name, field_names):
|
||||
|
||||
|
||||
def invalid_json_exit(file_name):
|
||||
"""Raises a sysyem exit when invalid JSON content is encountered."""
|
||||
"""Raises a system exit when invalid JSON content is encountered."""
|
||||
system_error_exit(17, MESSAGE_INVALID_JSON.format(file_name))
|
||||
|
||||
|
||||
|
||||
@@ -154,39 +154,66 @@ def write_csv_file(csvRows, titles, list_type, todrive):
|
||||
return True
|
||||
return False
|
||||
|
||||
if GC_Values[GC_CSV_ROW_FILTER]:
|
||||
for column, filterVal in iter(GC_Values[GC_CSV_ROW_FILTER].items()):
|
||||
if column not in titles:
|
||||
sys.stderr.write(
|
||||
f'WARNING: Row filter column "{column}" is not in output columns\n'
|
||||
)
|
||||
continue
|
||||
if filterVal[0] == 'regex':
|
||||
csvRows = [
|
||||
row for row in csvRows
|
||||
if filterVal[1].search(str(row.get(column, '')))
|
||||
]
|
||||
elif filterVal[0] == 'notregex':
|
||||
csvRows = [
|
||||
row for row in csvRows
|
||||
if not filterVal[1].search(str(row.get(column, '')))
|
||||
]
|
||||
elif filterVal[0] in ['date', 'time']:
|
||||
csvRows = [
|
||||
row for row in csvRows if rowDateTimeFilterMatch(
|
||||
filterVal[0] == 'date', row.get(column, ''),
|
||||
filterVal[1], filterVal[2])
|
||||
]
|
||||
elif filterVal[0] == 'count':
|
||||
csvRows = [
|
||||
row for row in csvRows if rowCountFilterMatch(
|
||||
row.get(column, 0), filterVal[1], filterVal[2])
|
||||
]
|
||||
else: #boolean
|
||||
csvRows = [
|
||||
row for row in csvRows if rowBooleanFilterMatch(
|
||||
row.get(column, False), filterVal[1])
|
||||
]
|
||||
def rowFilterMatch(filters, columns, row):
|
||||
for c, filterVal in iter(filters.items()):
|
||||
for column in columns[c]:
|
||||
if filterVal[1] == 'regex':
|
||||
if filterVal[2].search(str(row.get(column, ''))):
|
||||
return True
|
||||
elif filterVal[1] == 'notregex':
|
||||
if not filterVal[2].search(str(row.get(column, ''))):
|
||||
return True
|
||||
elif filterVal[1] in ['date', 'time']:
|
||||
if rowDateTimeFilterMatch(
|
||||
filterVal[1] == 'date', row.get(column, ''),
|
||||
filterVal[2], filterVal[3]):
|
||||
return True
|
||||
elif filterVal[1] == 'count':
|
||||
if rowCountFilterMatch(
|
||||
row.get(column, 0), filterVal[2], filterVal[3]):
|
||||
return True
|
||||
else: #boolean
|
||||
if rowBooleanFilterMatch(
|
||||
row.get(column, False), filterVal[2]):
|
||||
return True
|
||||
return False
|
||||
|
||||
if GC_Values[GC_CSV_ROW_FILTER] or GC_Values[GC_CSV_ROW_DROP_FILTER]:
|
||||
if GC_Values[GC_CSV_ROW_FILTER]:
|
||||
keepColumns = {}
|
||||
for column, filterVal in iter(GC_Values[GC_CSV_ROW_FILTER].items()):
|
||||
columns = [t for t in titles if filterVal[0].match(t)]
|
||||
if columns:
|
||||
keepColumns[column] = columns
|
||||
else:
|
||||
keepColumns[column] = [None]
|
||||
sys.stderr.write(
|
||||
f'WARNING: Row filter column pattern "{column}" does not match any output columns\n'
|
||||
)
|
||||
else:
|
||||
keepColumns = None
|
||||
if GC_Values[GC_CSV_ROW_DROP_FILTER]:
|
||||
dropColumns = {}
|
||||
for column, filterVal in iter(GC_Values[GC_CSV_ROW_DROP_FILTER].items()):
|
||||
columns = [t for t in titles if filterVal[0].match(t)]
|
||||
if columns:
|
||||
dropColumns[column] = columns
|
||||
else:
|
||||
dropColumns[column] = [None]
|
||||
sys.stderr.write(
|
||||
f'WARNING: Row drop filter column pattern "{column}" does not match any output columns\n'
|
||||
)
|
||||
else:
|
||||
dropColumns = None
|
||||
rows = []
|
||||
for row in csvRows:
|
||||
if (((keepColumns is None) or
|
||||
rowFilterMatch(GC_Values[GC_CSV_ROW_FILTER], keepColumns, row)) and
|
||||
((dropColumns is None) or
|
||||
not rowFilterMatch(GC_Values[GC_CSV_ROW_DROP_FILTER], dropColumns, row))):
|
||||
rows.append(row)
|
||||
csvRows = rows
|
||||
|
||||
if GC_Values[GC_CSV_HEADER_FILTER] or GC_Values[GC_CSV_HEADER_DROP_FILTER]:
|
||||
if GC_Values[GC_CSV_HEADER_DROP_FILTER]:
|
||||
titles = [
|
||||
@@ -288,7 +315,7 @@ def print_json(object_value, spacing=''):
|
||||
sys.stdout.write(f' {spacing}{i+1}) ')
|
||||
print_json(a_value, f' {spacing}')
|
||||
elif isinstance(object_value, dict):
|
||||
for key in ['kind', 'etag', 'etags']:
|
||||
for key in ['kind', 'etag', 'etags', '@type']:
|
||||
object_value.pop(key, None)
|
||||
for another_object, another_value in object_value.items():
|
||||
sys.stdout.write(f' {spacing}{another_object}: ')
|
||||
|
||||
@@ -218,6 +218,61 @@ def got_total_items_first_last_msg(items):
|
||||
return f'Got {TOTAL_ITEMS_MARKER} {items}: {FIRST_ITEM_MARKER} - {LAST_ITEM_MARKER}' + '\n'
|
||||
|
||||
|
||||
def process_page(page, items, all_items, total_items, page_message, message_attribute):
|
||||
"""Process one page of a Google service function response.
|
||||
|
||||
Append a list of items to the aggregate list of items
|
||||
|
||||
Args:
|
||||
page: list of items
|
||||
items: see get_all_pages
|
||||
all_items: aggregate list of items
|
||||
total_items: length of all_items
|
||||
page_message: see get_all_pages
|
||||
message_attribute: get_all_pages
|
||||
Returns:
|
||||
The page token and total number of items
|
||||
"""
|
||||
if page:
|
||||
page_token = page.get('nextPageToken')
|
||||
page_items = page.get(items, [])
|
||||
num_page_items = len(page_items)
|
||||
total_items += num_page_items
|
||||
if all_items is not None:
|
||||
all_items.extend(page_items)
|
||||
else:
|
||||
page_token = None
|
||||
num_page_items = 0
|
||||
|
||||
# Show a paging message to the user that indicates paging progress
|
||||
if page_message:
|
||||
show_message = page_message.replace(TOTAL_ITEMS_MARKER,
|
||||
str(total_items))
|
||||
if message_attribute:
|
||||
first_item = page_items[0] if num_page_items > 0 else {}
|
||||
last_item = page_items[-1] if num_page_items > 1 else first_item
|
||||
if isinstance(message_attribute, str):
|
||||
first_item = str(first_item.get(message_attribute, ''))
|
||||
last_item = str(last_item.get(message_attribute, ''))
|
||||
else:
|
||||
for attr in message_attribute:
|
||||
first_item = first_item.get(attr, {})
|
||||
last_item = last_item.get(attr, {})
|
||||
first_item = str(first_item)
|
||||
last_item = str(last_item)
|
||||
show_message = show_message.replace(FIRST_ITEM_MARKER, first_item)
|
||||
show_message = show_message.replace(LAST_ITEM_MARKER, last_item)
|
||||
sys.stderr.write('\r')
|
||||
sys.stderr.flush()
|
||||
sys.stderr.write(show_message)
|
||||
return (page_token, total_items)
|
||||
|
||||
def finalize_page_message(page_message):
|
||||
""" Issue final page_message """
|
||||
if page_message and (page_message[-1] != '\n'):
|
||||
sys.stderr.write('\r\n')
|
||||
sys.stderr.flush()
|
||||
|
||||
def get_all_pages(service,
|
||||
function,
|
||||
items='items',
|
||||
@@ -274,46 +329,12 @@ def get_all_pages(service,
|
||||
soft_errors=soft_errors,
|
||||
throw_reasons=throw_reasons,
|
||||
retry_reasons=retry_reasons,
|
||||
pageToken=page_token,
|
||||
**kwargs)
|
||||
if page:
|
||||
page_token = page.get('nextPageToken')
|
||||
page_items = page.get(items, [])
|
||||
num_page_items = len(page_items)
|
||||
total_items += num_page_items
|
||||
all_items.extend(page_items)
|
||||
else:
|
||||
page_token = None
|
||||
num_page_items = 0
|
||||
|
||||
# Show a paging message to the user that indicates paging progress
|
||||
if page_message:
|
||||
show_message = page_message.replace(TOTAL_ITEMS_MARKER,
|
||||
str(total_items))
|
||||
if message_attribute:
|
||||
first_item = page_items[0] if num_page_items > 0 else {}
|
||||
last_item = page_items[-1] if num_page_items > 1 else first_item
|
||||
if type(message_attribute) is str:
|
||||
first_item = str(first_item.get(message_attribute, ''))
|
||||
last_item = str(last_item.get(message_attribute, ''))
|
||||
else:
|
||||
for attr in message_attribute:
|
||||
first_item = first_item.get(attr, {})
|
||||
last_item = last_item.get(attr, {})
|
||||
first_item = str(first_item)
|
||||
last_item = str(last_item)
|
||||
show_message = show_message.replace(FIRST_ITEM_MARKER, first_item)
|
||||
show_message = show_message.replace(LAST_ITEM_MARKER, last_item)
|
||||
sys.stderr.write('\r')
|
||||
sys.stderr.flush()
|
||||
sys.stderr.write(show_message)
|
||||
|
||||
page_token, total_items = process_page(page, items, all_items, total_items, page_message, message_attribute)
|
||||
if not page_token:
|
||||
# End the paging status message and return all items.
|
||||
if page_message and (page_message[-1] != '\n'):
|
||||
sys.stderr.write('\r\n')
|
||||
sys.stderr.flush()
|
||||
finalize_page_message(page_message)
|
||||
return all_items
|
||||
kwargs['pageToken'] = page_token
|
||||
|
||||
|
||||
# TODO: Make this private once all execution related items that use this method
|
||||
|
||||
@@ -154,7 +154,11 @@ def delACL():
|
||||
gapi.call(cal.acl(), 'delete', calendarId=calendarId, ruleId=ruleId)
|
||||
else:
|
||||
body = {'role': 'none'}
|
||||
_getCalendarACLScope(5, body)
|
||||
i = 4
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in CALENDAR_ACL_ROLES_MAP:
|
||||
i += 1
|
||||
_getCalendarACLScope(i, body)
|
||||
print(f'Calendar: {calendarId}, Delete ACL: {formatACLScope(body)}')
|
||||
gapi.call(cal.acl(),
|
||||
'insert',
|
||||
@@ -824,7 +828,7 @@ def _showCalendar(userCalendar, j, jcount):
|
||||
print(f' Color ID: {userCalendar["colorId"]}, ' \
|
||||
f'Background Color: {userCalendar["backgroundColor"]}, ' \
|
||||
f'Foreground Color: {userCalendar["foregroundColor"]}')
|
||||
print(f' Default Reminders:')
|
||||
print(' Default Reminders:')
|
||||
for reminder in userCalendar.get('defaultReminders', []):
|
||||
print(f' Method: {reminder["method"]}, ' \
|
||||
f'Minutes: {reminder["minutes"]}')
|
||||
|
||||
303
src/gam/gapi/cbcm.py
Normal file
303
src/gam/gapi/cbcm.py
Normal file
@@ -0,0 +1,303 @@
|
||||
"""Chrome Browser Cloud Management API calls"""
|
||||
|
||||
import csv
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
import gam
|
||||
from gam.var import *
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import fileutils
|
||||
from gam import gapi
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
from gam import utils
|
||||
|
||||
|
||||
def _get_customerid():
|
||||
''' returns customer id without C prefix'''
|
||||
customer_id = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer_id[0] == 'C':
|
||||
customer_id = customer_id[1:]
|
||||
return customer_id
|
||||
|
||||
|
||||
def build():
|
||||
return gam.buildGAPIObject('cbcm')
|
||||
|
||||
|
||||
def delete():
|
||||
cbcm = build()
|
||||
device_id = sys.argv[3]
|
||||
customer_id = _get_customerid()
|
||||
gapi.call(cbcm.chromebrowsers(), 'delete', deviceId=device_id,
|
||||
customer=customer_id)
|
||||
print(f'Deleted browser {device_id}')
|
||||
|
||||
|
||||
def info():
|
||||
cbcm = build()
|
||||
device_id = sys.argv[3]
|
||||
projection = 'BASIC'
|
||||
fields = None
|
||||
customer_id = _get_customerid()
|
||||
i = 4
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['basic', 'full']:
|
||||
projection = myarg.upper()
|
||||
i += 1
|
||||
elif myarg == 'fields':
|
||||
fields = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam info browser')
|
||||
browser = gapi.call(cbcm.chromebrowsers(), 'get',
|
||||
customer=customer_id,
|
||||
fields=fields, deviceId=device_id,
|
||||
projection=projection)
|
||||
display.print_json(browser)
|
||||
|
||||
|
||||
def move():
|
||||
cbcm = build()
|
||||
body = {'resource_ids': []}
|
||||
customer_id = _get_customerid()
|
||||
i = 3
|
||||
resource_ids = []
|
||||
batch_size = 600
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'ids':
|
||||
resource_ids.extend(sys.argv[i + 1].split(','))
|
||||
i += 2
|
||||
elif myarg == 'query':
|
||||
query = sys.argv[i + 1]
|
||||
page_message = gapi.got_total_items_msg('Browsers', '...\n')
|
||||
browsers = gapi.get_all_pages(cbcm.chromebrowsers(), 'list',
|
||||
'browsers', page_message=page_message,
|
||||
customer=customer_id,
|
||||
query=query, projection='BASIC',
|
||||
fields='browsers(deviceId),nextPageToken')
|
||||
ids = [browser['deviceId'] for browser in browsers]
|
||||
resource_ids.extend(ids)
|
||||
i += 2
|
||||
elif myarg == 'file':
|
||||
with fileutils.open_file(sys.argv[i+1], strip_utf_bom=True) as filed:
|
||||
for row in filed:
|
||||
rid = row.strip()
|
||||
if rid:
|
||||
resource_ids.append(rid)
|
||||
i += 2
|
||||
elif myarg == 'csvfile':
|
||||
drive, fname_column = os.path.splitdrive(sys.argv[i+1])
|
||||
if fname_column.find(':') == -1:
|
||||
controlflow.system_error_exit(
|
||||
2, 'Expected csvfile FileName:FieldName')
|
||||
(filename, column) = fname_column.split(':')
|
||||
with fileutils.open_file(drive + filename) as filed:
|
||||
input_file = csv.DictReader(filed, restval='')
|
||||
if column not in input_file.fieldnames:
|
||||
controlflow.csv_field_error_exit(column,
|
||||
input_file.fieldnames)
|
||||
for row in input_file:
|
||||
rid = row[column].strip()
|
||||
if rid:
|
||||
resource_ids.append(rid)
|
||||
i += 2
|
||||
elif myarg in ['ou', 'orgunit', 'org']:
|
||||
org_unit = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1])
|
||||
body['org_unit_path'] = org_unit
|
||||
i += 2
|
||||
elif myarg == 'batchsize':
|
||||
batch_size = int(sys.argv[i+1])
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam move browsers')
|
||||
if 'org_unit_path' not in body:
|
||||
controlflow.missing_argument_exit('ou', 'gam move browsers')
|
||||
elif not resource_ids:
|
||||
controlflow.missing_argument_exit('query or ids',
|
||||
'gam move browsers')
|
||||
# split moves into max 600 devices per batch
|
||||
for chunk in range(0, len(resource_ids), batch_size):
|
||||
body['resource_ids'] = resource_ids[chunk:chunk + batch_size]
|
||||
print(f' moving {len(body["resource_ids"])} browsers to ' \
|
||||
f'{body["org_unit_path"]}')
|
||||
gapi.call(cbcm.chromebrowsers(), 'moveChromeBrowsersToOu',
|
||||
customer=customer_id, body=body)
|
||||
|
||||
|
||||
def print_():
|
||||
cbcm = build()
|
||||
customer_id = _get_customerid()
|
||||
projection = 'BASIC'
|
||||
orgUnitPath = query = None
|
||||
fields = None
|
||||
titles = []
|
||||
csv_rows = []
|
||||
todrive = False
|
||||
sort_headers = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'query':
|
||||
query = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg in ['ou', 'org', 'orgunit']:
|
||||
orgUnitPath = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1], pathOnly=True, absolutePath=True)
|
||||
i += 2
|
||||
elif myarg == 'projection':
|
||||
projection = sys.argv[i + 1].upper()
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg == 'sortheaders':
|
||||
sort_headers = True
|
||||
i += 1
|
||||
elif myarg == 'fields':
|
||||
fields = sys.argv[i + 1].replace(',', ' ').split()
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam print browsers')
|
||||
if fields:
|
||||
fields.append('deviceId')
|
||||
fields = f'browsers({",".join(set(fields))}),nextPageToken'
|
||||
page_message = gapi.got_total_items_msg('Browsers', '...\n')
|
||||
browsers = gapi.get_all_pages(cbcm.chromebrowsers(), 'list',
|
||||
'browsers', page_message=page_message,
|
||||
customer=customer_id,
|
||||
orgUnitPath=orgUnitPath, query=query, projection=projection,
|
||||
fields=fields)
|
||||
for browser in browsers:
|
||||
browser = utils.flatten_json(browser)
|
||||
for a_key in browser:
|
||||
if a_key not in titles:
|
||||
titles.append(a_key)
|
||||
csv_rows.append(browser)
|
||||
if sort_headers:
|
||||
display.sort_csv_titles(['deviceId',], titles)
|
||||
display.write_csv_file(csv_rows, titles, 'Browsers', todrive)
|
||||
|
||||
|
||||
attributes = {
|
||||
'assetid': 'annotatedAssetId',
|
||||
'location': 'annotatedLocation',
|
||||
'notes': 'annotatedNotes',
|
||||
'user': 'annotatedUser'
|
||||
}
|
||||
attribute_fields = ','.join(list(attributes.values()))
|
||||
|
||||
def update():
|
||||
cbcm = build()
|
||||
customer_id = _get_customerid()
|
||||
device_id = sys.argv[3]
|
||||
body = {'deviceId': device_id}
|
||||
i = 4
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in attributes:
|
||||
body[attributes[myarg]] = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam update browser')
|
||||
browser = gapi.call(cbcm.chromebrowsers(), 'get', deviceId=device_id,
|
||||
customer=customer_id,
|
||||
projection='BASIC', fields=attribute_fields)
|
||||
browser.update(body)
|
||||
result = gapi.call(cbcm.chromebrowsers(), 'update', deviceId=device_id,
|
||||
customer=customer_id, body=browser,
|
||||
projection='BASIC', fields="deviceId")
|
||||
print(f'Updated browser {result["deviceId"]}')
|
||||
|
||||
|
||||
def createtoken():
|
||||
cbcm = build()
|
||||
customer_id = _get_customerid()
|
||||
body = {'token_type': 'CHROME_BROWSER'}
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['ou', 'orgunit', 'org']:
|
||||
body['org_unit_path'] = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['expire', 'expires']:
|
||||
body['expire_time'] = utils.get_time_or_delta_from_now(sys.argv[i + 1])
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam create browsertoken')
|
||||
browser = gapi.call(cbcm.enrollmentTokens(), 'create',
|
||||
customer=customer_id, body=body)
|
||||
print(f'Created browser enrollment token {browser["token"]}')
|
||||
|
||||
|
||||
def revoketoken():
|
||||
cbcm = build()
|
||||
customer_id = _get_customerid()
|
||||
token_permanent_id = sys.argv[3]
|
||||
gapi.call(cbcm.enrollmentTokens(), 'revoke', tokenPermanentId=token_permanent_id,
|
||||
customer=customer_id)
|
||||
print(f'Deleted browser enrollment token {token_permanent_id}')
|
||||
|
||||
|
||||
def printshowtokens(csvFormat):
|
||||
cbcm = build()
|
||||
customer_id = _get_customerid()
|
||||
query = None
|
||||
fields = None
|
||||
if csvFormat:
|
||||
titles = ['token']
|
||||
csv_rows = []
|
||||
todrive = False
|
||||
sort_headers = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'query':
|
||||
query = sys.argv[i+1]
|
||||
i += 2
|
||||
elif csvFormat and myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif csvFormat and myarg == 'sortheaders':
|
||||
sort_headers = True
|
||||
i += 1
|
||||
elif myarg == 'fields':
|
||||
fields = sys.argv[i + 1].replace(',', ' ').split()
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
f"gam {['show', 'print'][csvFormat]} browsertokens")
|
||||
if fields:
|
||||
fields.append('token')
|
||||
fields = f'chromeEnrollmentTokens({",".join(set(fields))}),nextPageToken'
|
||||
page_message = gapi.got_total_items_msg('Chrome Browser Enrollment Tokens', '...\n')
|
||||
browsers = gapi.get_all_pages(cbcm.enrollmentTokens(), 'list',
|
||||
'chromeEnrollmentTokens', page_message=page_message,
|
||||
customer=customer_id,
|
||||
query=query, fields=fields)
|
||||
if not csvFormat:
|
||||
count = len(browsers)
|
||||
print(f'Show {count} Chrome Browser Enrollment Tokens')
|
||||
i = 0
|
||||
for browser in browsers:
|
||||
i += 1
|
||||
print(f' Chrome Browser Enrollment Token: {browser["token"]}{gam.currentCount(i, count)}')
|
||||
browser.pop('kind', None)
|
||||
for field in browser:
|
||||
print(f' {field}: {browser[field]}')
|
||||
else:
|
||||
for browser in browsers:
|
||||
browser = utils.flatten_json(browser)
|
||||
for a_key in browser:
|
||||
if a_key not in titles:
|
||||
titles.append(a_key)
|
||||
csv_rows.append(browser)
|
||||
if sort_headers:
|
||||
display.sort_csv_titles(['token',], titles)
|
||||
display.write_csv_file(csv_rows, titles, 'Chrome Browser Enrollment Tokens', todrive)
|
||||
318
src/gam/gapi/chromepolicy.py
Normal file
318
src/gam/gapi/chromepolicy.py
Normal file
@@ -0,0 +1,318 @@
|
||||
"""Chrome Browser Cloud Management API calls"""
|
||||
|
||||
import sys
|
||||
|
||||
import googleapiclient.errors
|
||||
|
||||
import gam
|
||||
from gam.var import GC_CUSTOMER_ID, GC_Values, MY_CUSTOMER
|
||||
from gam import controlflow
|
||||
from gam import gapi
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
from gam import utils
|
||||
|
||||
|
||||
def _get_customerid():
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer != MY_CUSTOMER and customer[0] != 'C':
|
||||
customer = 'C' + customer
|
||||
return f'customers/{customer}'
|
||||
|
||||
|
||||
def _get_orgunit(orgunit):
|
||||
if orgunit.startswith('orgunits/'):
|
||||
return orgunit
|
||||
_, orgunitid = gapi_directory_orgunits.getOrgUnitId(orgunit)
|
||||
return f'orgunits/{orgunitid[3:]}'
|
||||
|
||||
|
||||
def build():
|
||||
return gam.buildGAPIObject('chromepolicy')
|
||||
|
||||
|
||||
def printshow_policies():
|
||||
svc = build()
|
||||
customer = _get_customerid()
|
||||
orgunit = None
|
||||
printer_id = None
|
||||
app_id = None
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['ou', 'org', 'orgunit']:
|
||||
orgunit = _get_orgunit(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'printerid':
|
||||
printer_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'appid':
|
||||
app_id = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam print chromepolicy"'
|
||||
controlflow.system_error_exit(3, msg)
|
||||
if not orgunit:
|
||||
controlflow.system_error_exit(3, 'You must specify an orgunit')
|
||||
body = {
|
||||
'policyTargetKey': {
|
||||
'targetResource': orgunit,
|
||||
}
|
||||
}
|
||||
if printer_id:
|
||||
body['policyTargetKey']['additionalTargetKeys'] = {'printer_id': printer_id}
|
||||
namespaces = ['chrome.printers']
|
||||
elif app_id:
|
||||
body['policyTargetKey']['additionalTargetKeys'] = {'app_id': app_id}
|
||||
namespaces = ['chrome.users.apps',
|
||||
'chrome.devices.managedGuest.apps',
|
||||
'chrome.devices.kiosk.apps']
|
||||
else:
|
||||
namespaces = [
|
||||
'chrome.users',
|
||||
# Not yet implemented:
|
||||
# 'chrome.devices',
|
||||
# 'chrome.devices.managedGuest',
|
||||
# 'chrome.devices.kiosk',
|
||||
]
|
||||
throw_reasons = [gapi_errors.ErrorReason.FOUR_O_O,]
|
||||
orgunitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit[9:], None)
|
||||
header = f'Organizational Unit: {orgunitPath}'
|
||||
if printer_id:
|
||||
header += f', printerid: {printer_id}'
|
||||
elif app_id:
|
||||
header += f', appid: {app_id}'
|
||||
print(header)
|
||||
for namespace in namespaces:
|
||||
body['policySchemaFilter'] = f'{namespace}.*'
|
||||
try:
|
||||
policies = gapi.get_all_pages(svc.customers().policies(), 'resolve',
|
||||
items='resolvedPolicies',
|
||||
throw_reasons=throw_reasons,
|
||||
customer=customer,
|
||||
body=body)
|
||||
except googleapiclient.errors.HttpError:
|
||||
policies = []
|
||||
for policy in sorted(policies, key=lambda k: k.get('value', {}).get('policySchema', '')):
|
||||
print()
|
||||
name = policy.get('value', {}).get('policySchema', '')
|
||||
print(name)
|
||||
values = policy.get('value', {}).get('value', {})
|
||||
for setting, value in values.items():
|
||||
if isinstance(value, str) and value.find('_ENUM_') != -1:
|
||||
value = value.split('_ENUM_')[-1]
|
||||
print(f' {setting}: {value}')
|
||||
|
||||
|
||||
def build_schemas(svc=None, sfilter=None):
|
||||
if not svc:
|
||||
svc = build()
|
||||
parent = _get_customerid()
|
||||
schemas = gapi.get_all_pages(svc.customers().policySchemas(), 'list',
|
||||
items='policySchemas', parent=parent, filter=sfilter)
|
||||
schema_objects = {}
|
||||
for schema in schemas:
|
||||
schema_name = schema.get('name', '').split('/')[-1]
|
||||
schema_dict = {
|
||||
'name': schema_name,
|
||||
'description': schema.get('policyDescription', ''),
|
||||
'settings': {},
|
||||
}
|
||||
field_descriptions = schema.get('fieldDescriptions', [])
|
||||
for mtype in schema.get('definition', {}).get('messageType', {}):
|
||||
for setting in mtype.get('field', {}):
|
||||
setting_name = setting.get('name', '')
|
||||
setting_dict = {
|
||||
'name': setting_name,
|
||||
'constraints': None,
|
||||
'descriptions': [],
|
||||
'type': setting.get('type'),
|
||||
}
|
||||
if setting_dict['type'] == 'TYPE_STRING' and \
|
||||
setting.get('label') == 'LABEL_REPEATED':
|
||||
setting_dict['type'] = 'TYPE_LIST'
|
||||
if setting_dict['type'] == 'TYPE_ENUM':
|
||||
type_name = setting['typeName']
|
||||
for an_enum in schema['definition']['enumType']:
|
||||
if an_enum['name'] == type_name:
|
||||
setting_dict['enums'] = [enum['name'] for enum in an_enum['value']]
|
||||
setting_dict['enum_prefix'] = utils.commonprefix(setting_dict['enums'])
|
||||
prefix_len = len(setting_dict['enum_prefix'])
|
||||
setting_dict['enums'] = [enum[prefix_len:] for enum \
|
||||
in setting_dict['enums'] \
|
||||
if not enum.endswith('UNSPECIFIED')]
|
||||
setting_dict['descriptions'] = ['']*len(setting_dict['enums'])
|
||||
if field_descriptions:
|
||||
for i, an in enumerate(setting_dict['enums']):
|
||||
for fdesc in field_descriptions:
|
||||
if fdesc.get('field') == setting_name:
|
||||
for d in fdesc.get('knownValueDescriptions', []):
|
||||
if d['value'][prefix_len:] == an:
|
||||
setting_dict['descriptions'][i] = d['description']
|
||||
break
|
||||
break
|
||||
break
|
||||
elif setting_dict['type'] == 'TYPE_MESSAGE':
|
||||
continue
|
||||
else:
|
||||
setting_dict['enums'] = None
|
||||
for fdesc in schema.get('fieldDescriptions', []):
|
||||
if fdesc.get('field') == setting_name:
|
||||
if 'knownValueDescriptions' in fdesc:
|
||||
setting_dict['descriptions'] = fdesc['knownValueDescriptions']
|
||||
elif 'description' in fdesc:
|
||||
setting_dict['descriptions'] = [fdesc['description']]
|
||||
schema_dict['settings'][setting_name.lower()] = setting_dict
|
||||
schema_objects[schema_name.lower()] = schema_dict
|
||||
return schema_objects
|
||||
|
||||
|
||||
def printshow_schemas():
|
||||
svc = build()
|
||||
sfilter = None
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'filter':
|
||||
sfilter = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam print chromeschema"'
|
||||
controlflow.system_error_exit(3, msg)
|
||||
schemas = build_schemas(svc, sfilter)
|
||||
for _, value in sorted(iter(schemas.items())):
|
||||
print(f'{value.get("name")}: {value.get("description")}')
|
||||
for val in value['settings'].values():
|
||||
vtype = val.get('type')
|
||||
print(f' {val.get("name")}: {vtype}')
|
||||
if vtype == 'TYPE_ENUM':
|
||||
enums = val.get('enums', [])
|
||||
descriptions = val.get('descriptions', [])
|
||||
for i in range(len(val.get('enums', []))):
|
||||
print(f' {enums[i]}: {descriptions[i]}')
|
||||
elif vtype == 'TYPE_BOOL':
|
||||
pvs = val.get('descriptions')
|
||||
for pvi in pvs:
|
||||
if isinstance(pvi, dict):
|
||||
pvalue = pvi.get('value')
|
||||
pdescription = pvi.get('description')
|
||||
print(f' {pvalue}: {pdescription}')
|
||||
elif isinstance(pvi, list):
|
||||
print(f' {pvi[0]}')
|
||||
else:
|
||||
description = val.get('descriptions')
|
||||
if len(description) > 0:
|
||||
print(f' {description[0]}')
|
||||
print()
|
||||
|
||||
|
||||
def delete_policy():
|
||||
svc = build()
|
||||
customer = _get_customerid()
|
||||
schemas = build_schemas(svc)
|
||||
orgunit = None
|
||||
printer_id = None
|
||||
app_id = None
|
||||
i = 3
|
||||
body = {'requests': []}
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['ou', 'org', 'orgunit']:
|
||||
orgunit = _get_orgunit(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'printerid':
|
||||
printer_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'appid':
|
||||
app_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg in schemas:
|
||||
body['requests'].append({'policySchema': schemas[myarg]['name']})
|
||||
i += 1
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam delete chromepolicy"'
|
||||
controlflow.system_error_exit(3, msg)
|
||||
if not orgunit:
|
||||
controlflow.system_error_exit(3, 'You must specify an orgunit')
|
||||
for request in body['requests']:
|
||||
request['policyTargetKey'] = {'targetResource': orgunit}
|
||||
if printer_id:
|
||||
request['policyTargetKey']['additionalTargetKeys'] = {'printer_id': printer_id}
|
||||
elif app_id:
|
||||
request['policyTargetKey']['additionalTargetKeys'] = {'app_id': app_id}
|
||||
gapi.call(svc.customers().policies().orgunits(), 'batchInherit', customer=customer, body=body)
|
||||
|
||||
|
||||
def update_policy():
|
||||
svc = build()
|
||||
customer = _get_customerid()
|
||||
schemas = build_schemas(svc)
|
||||
orgunit = None
|
||||
printer_id = None
|
||||
app_id = None
|
||||
i = 3
|
||||
body = {'requests': []}
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['ou', 'org', 'orgunit']:
|
||||
orgunit = _get_orgunit(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'printerid':
|
||||
printer_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'appid':
|
||||
app_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg in schemas:
|
||||
body['requests'].append({'policyValue': {'policySchema': schemas[myarg]['name'],
|
||||
'value': {}},
|
||||
'updateMask': ''})
|
||||
i += 1
|
||||
while i < len(sys.argv):
|
||||
field = sys.argv[i].lower()
|
||||
if field in ['ou', 'org', 'orgunit', 'printerid', 'appid'] or '.' in field:
|
||||
break # field is actually a new policy, orgunit or app/printer id
|
||||
expected_fields = ', '.join(schemas[myarg]['settings'])
|
||||
if field not in expected_fields:
|
||||
msg = f'Expected {myarg} field of {expected_fields}. Got {field}.'
|
||||
controlflow.system_error_exit(4, msg)
|
||||
cased_field = schemas[myarg]['settings'][field]['name']
|
||||
value = sys.argv[i+1]
|
||||
vtype = schemas[myarg]['settings'][field]['type']
|
||||
if vtype in ['TYPE_INT64', 'TYPE_INT32', 'TYPE_UINT64']:
|
||||
if not value.isnumeric():
|
||||
msg = f'Value for {myarg} {field} must be a number, got {value}'
|
||||
controlflow.system_error_exit(7, msg)
|
||||
value = int(value)
|
||||
elif vtype in ['TYPE_BOOL']:
|
||||
value = gam.getBoolean(value, field)
|
||||
elif vtype in ['TYPE_ENUM']:
|
||||
value = value.upper()
|
||||
enum_values = schemas[myarg]['settings'][field]['enums']
|
||||
if value not in enum_values:
|
||||
expected_enums = ', '.join(enum_values)
|
||||
msg = f'Expected {myarg} {field} value to be one of ' \
|
||||
f'{expected_enums}, got {value}'
|
||||
controlflow.system_error_exit(8, msg)
|
||||
prefix = schemas[myarg]['settings'][field]['enum_prefix']
|
||||
value = f'{prefix}{value}'
|
||||
elif vtype in ['TYPE_LIST']:
|
||||
value = value.split(',')
|
||||
body['requests'][-1]['policyValue']['value'][cased_field] = value
|
||||
body['requests'][-1]['updateMask'] += f'{cased_field},'
|
||||
i += 2
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam update chromepolicy"'
|
||||
controlflow.system_error_exit(4, msg)
|
||||
if not orgunit:
|
||||
controlflow.system_error_exit(3, 'You must specify an orgunit')
|
||||
for request in body['requests']:
|
||||
request['policyTargetKey'] = {'targetResource': orgunit}
|
||||
if printer_id:
|
||||
request['policyTargetKey']['additionalTargetKeys'] = {'printer_id': printer_id}
|
||||
elif app_id:
|
||||
request['policyTargetKey']['additionalTargetKeys'] = {'app_id': app_id}
|
||||
gapi.call(svc.customers().policies().orgunits(),
|
||||
'batchModify',
|
||||
customer=customer,
|
||||
body=body)
|
||||
@@ -3,6 +3,7 @@ import sys
|
||||
|
||||
import googleapiclient
|
||||
|
||||
import gam
|
||||
from gam.var import *
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
@@ -11,7 +12,7 @@ from gam import gapi
|
||||
from gam import utils
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi import cloudidentity as gapi_cloudidentity
|
||||
|
||||
from gam.gapi.directory import customer as gapi_directory_customer
|
||||
|
||||
def _get_device_customerid():
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
@@ -49,6 +50,7 @@ def create():
|
||||
result = gapi.call(ci.devices(), 'create', customer=customer, body=body)
|
||||
print(f'Created device {result["response"]["name"]}')
|
||||
|
||||
|
||||
def _get_device_name():
|
||||
name = sys.argv[3]
|
||||
if name == 'id':
|
||||
@@ -65,10 +67,16 @@ def info():
|
||||
device = gapi.call(ci.devices(), 'get', name=name, customer=customer)
|
||||
device_users = gapi.get_all_pages(ci.devices().deviceUsers(), 'list',
|
||||
'deviceUsers', parent=name, customer=customer)
|
||||
for device_user in device_users:
|
||||
parent = device_user['name']
|
||||
device_user['client_states'] = gapi.get_all_pages(
|
||||
ci.devices().deviceUsers().clientStates(),
|
||||
'list', 'clientStates', parent=parent, customer=customer)
|
||||
display.print_json(device)
|
||||
print('Device Users:')
|
||||
display.print_json(device_users)
|
||||
|
||||
|
||||
def _generic_action(action, device_user=False):
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
customer = _get_device_customerid()
|
||||
@@ -87,30 +95,153 @@ def _generic_action(action, device_user=False):
|
||||
op = gapi.call(endpoint, action, name=name, **kwargs)
|
||||
print(op)
|
||||
|
||||
|
||||
def delete():
|
||||
_generic_action('delete')
|
||||
|
||||
|
||||
def cancel_wipe():
|
||||
_generic_action('cancelWipe')
|
||||
|
||||
|
||||
def wipe():
|
||||
_generic_action('wipe')
|
||||
|
||||
|
||||
def approve_user():
|
||||
_generic_action('approve', True)
|
||||
|
||||
|
||||
def block_user():
|
||||
_generic_action('block', True)
|
||||
|
||||
|
||||
def cancel_wipe_user():
|
||||
_generic_action('cancelWipe', True)
|
||||
|
||||
|
||||
def delete_user():
|
||||
_generic_action('delete', True)
|
||||
|
||||
|
||||
def wipe_user():
|
||||
_generic_action('wipe', True)
|
||||
|
||||
|
||||
def _get_deviceuser_name():
|
||||
i = 3
|
||||
name = sys.argv[i]
|
||||
if name == 'id':
|
||||
i += 1
|
||||
name = sys.argv[i]
|
||||
if not name.startswith('devices/'):
|
||||
name = f'devices/{name}'
|
||||
return (i+1, name)
|
||||
|
||||
def info_state():
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
customer = _get_device_customerid()
|
||||
customer_id = customer[10:]
|
||||
client_id = f'{customer_id}-gam'
|
||||
i, deviceuser = _get_deviceuser_name()
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'clientid':
|
||||
client_id = f'{customer_id}-{sys.argv[i+1]}'
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam info deviceuserstate')
|
||||
name = f'{deviceuser}/clientStates/{client_id}'
|
||||
result = gapi.call(ci.devices().deviceUsers().clientStates(), 'get',
|
||||
name=name, customer=customer)
|
||||
display.print_json(result)
|
||||
|
||||
|
||||
def update_state():
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
customer = _get_device_customerid()
|
||||
customer_id = customer[10:]
|
||||
client_id = f'{customer_id}-gam'
|
||||
body = {}
|
||||
i, deviceuser = _get_deviceuser_name()
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'clientid':
|
||||
client_id = f'{customer_id}-{sys.argv[i+1]}'
|
||||
i += 2
|
||||
elif myarg in ['assettag', 'assettags']:
|
||||
body['assetTags'] = gam.shlexSplitList(sys.argv[i+1])
|
||||
if body['assetTags'] == ['clear']:
|
||||
# TODO: this doesn't work to clear
|
||||
# existing values. Figure out why.
|
||||
body['assetTags'] = [None]
|
||||
i += 2
|
||||
elif myarg in ['compliantstate', 'compliancestate']:
|
||||
comp_states = gapi.get_enum_values_minus_unspecified(
|
||||
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1ClientState']['properties']['complianceState']['enum'])
|
||||
body['complianceState'] = sys.argv[i+1].upper()
|
||||
if body['complianceState'] not in comp_states:
|
||||
controlflow.expected_argument_exit('compliant_state',
|
||||
', '.join(comp_states),
|
||||
sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'customid':
|
||||
body['customId'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'healthscore':
|
||||
health_scores = gapi.get_enum_values_minus_unspecified(
|
||||
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1ClientState']['properties']['healthScore']['enum'])
|
||||
body['healthScore'] = sys.argv[i+1].upper()
|
||||
if body['healthScore'] == 'CLEAR':
|
||||
body['healthScore'] = None
|
||||
if body['healthScore'] and body['healthScore'] not in health_scores:
|
||||
controlflow.expected_argument_exit('health_score',
|
||||
', '.join(health_scores),
|
||||
sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'customvalue':
|
||||
allowed_types = ['bool', 'number', 'string']
|
||||
value_type = sys.argv[i+1].lower()
|
||||
if value_type not in allowed_types:
|
||||
controlflow.expected_argument_exit('custom_value',
|
||||
', '.join(allowed_types),
|
||||
sys.argv[i+1])
|
||||
key = sys.argv[i+2]
|
||||
value = sys.argv[i+3]
|
||||
if value_type == 'bool':
|
||||
value = gam.getBoolean(value, key)
|
||||
elif value_type == 'number':
|
||||
value = int(value)
|
||||
body.setdefault('keyValuePairs', {})
|
||||
body['keyValuePairs'][key] = {f'{value_type}Value': value}
|
||||
i += 4
|
||||
elif myarg in ['managedstate']:
|
||||
managed_states = gapi.get_enum_values_minus_unspecified(
|
||||
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1ClientState']['properties']['managed']['enum'])
|
||||
body['managed'] = sys.argv[i+1].upper()
|
||||
if body['managed'] == 'CLEAR':
|
||||
body['managed'] = None
|
||||
if body['managed'] and body['managed'] not in managed_states:
|
||||
controlflow.expected_argument_exit('managed_state',
|
||||
', '.join(managed_states),
|
||||
sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg in ['scorereason']:
|
||||
body['scoreReason'] = sys.argv[i+1]
|
||||
if body['scoreReason'] == 'clear':
|
||||
body['scoreReason'] = None
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam update deviceuserstate')
|
||||
name = f'{deviceuser}/clientStates/{client_id}'
|
||||
updateMask = ','.join(body.keys())
|
||||
result = gapi.call(ci.devices().deviceUsers().clientStates(), 'patch',
|
||||
name=name, customer=customer, updateMask=updateMask, body=body)
|
||||
display.print_json(result)
|
||||
|
||||
|
||||
def print_():
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
customer = _get_device_customerid()
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import sys
|
||||
|
||||
import googleapiclient
|
||||
|
||||
import gam
|
||||
from gam.var import *
|
||||
from gam import controlflow
|
||||
@@ -160,6 +164,7 @@ def print_():
|
||||
members = membersCountOnly = managers = managersCountOnly = owners = ownersCountOnly = False
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||
usemember = None
|
||||
memberDelimiter = '\n'
|
||||
todrive = False
|
||||
titles = []
|
||||
@@ -171,6 +176,10 @@ def print_():
|
||||
if myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg == 'enterprisemember':
|
||||
member = gam.convertUIDtoEmailAddress(sys.argv[i + 1], email_types=['user', 'group'])
|
||||
usemember = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels"
|
||||
i += 2
|
||||
elif myarg == 'delimiter':
|
||||
memberDelimiter = sys.argv[i + 1]
|
||||
i += 2
|
||||
@@ -222,16 +231,36 @@ def print_():
|
||||
display.add_titles_to_csv_file([
|
||||
'Owners',
|
||||
], titles)
|
||||
gam.printGettingAllItems('Groups', None)
|
||||
gam.printGettingAllItems('Groups', usemember)
|
||||
page_message = gapi.got_total_items_first_last_msg('Groups')
|
||||
entityList = gapi.get_all_pages(ci.groups(),
|
||||
'list',
|
||||
'groups',
|
||||
page_message=page_message,
|
||||
message_attribute=['groupKey', 'id'],
|
||||
parent=parent,
|
||||
view='FULL',
|
||||
pageSize=500)
|
||||
if usemember:
|
||||
try:
|
||||
result = gapi.get_all_pages(ci.groups().memberships(),
|
||||
'searchTransitiveGroups',
|
||||
'memberships',
|
||||
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O],
|
||||
page_message=page_message,
|
||||
message_attribute=['groupKey', 'id'],
|
||||
parent='groups/-', query=usemember,
|
||||
fields='nextPageToken,memberships(group,groupKey(id),relationType)',
|
||||
pageSize=1000)
|
||||
except googleapiclient.errors.HttpError:
|
||||
controlflow.system_error_exit(
|
||||
2,
|
||||
f'enterprisemember requires Enterprise license')
|
||||
entityList = []
|
||||
for entity in result:
|
||||
if entity['relationType'] == 'DIRECT':
|
||||
entityList.append(gapi.call(ci.groups(), 'get', name=entity['group']))
|
||||
else:
|
||||
entityList = gapi.get_all_pages(ci.groups(),
|
||||
'list',
|
||||
'groups',
|
||||
page_message=page_message,
|
||||
message_attribute=['groupKey', 'id'],
|
||||
parent=parent,
|
||||
view='FULL',
|
||||
pageSize=500)
|
||||
i = 0
|
||||
count = len(entityList)
|
||||
for groupEntity in entityList:
|
||||
@@ -319,6 +348,7 @@ def print_members():
|
||||
todrive = False
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||
usemember = None
|
||||
roles = []
|
||||
titles = ['group']
|
||||
csvRows = []
|
||||
@@ -339,6 +369,10 @@ def print_members():
|
||||
f'{role} is not a valid role for "gam print group-members {myarg}"'
|
||||
)
|
||||
i += 2
|
||||
elif myarg == 'enterprisemember':
|
||||
member = gam.convertUIDtoEmailAddress(sys.argv[i + 1], email_types=['user', 'group'])
|
||||
usemember = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels"
|
||||
i += 2
|
||||
elif myarg in ['cigroup', 'cigroups']:
|
||||
group_email = gam.normalizeEmailAddressOrUID(sys.argv[i + 1])
|
||||
groups_to_get = [group_email]
|
||||
@@ -347,19 +381,36 @@ def print_members():
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam print cigroup-members')
|
||||
if not groups_to_get:
|
||||
gam.printGettingAllItems('Groups', None)
|
||||
gam.printGettingAllItems('Groups', usemember)
|
||||
page_message = gapi.got_total_items_first_last_msg('Groups')
|
||||
groups_to_get = gapi.get_all_pages(
|
||||
ci.groups(),
|
||||
'list',
|
||||
'groups',
|
||||
message_attribute=['groupKey', 'id'],
|
||||
page_message=page_message,
|
||||
parent=parent,
|
||||
view='BASIC',
|
||||
pageSize=1000,
|
||||
fields='nextPageToken,groups(groupKey(id))')
|
||||
groups_to_get = [group['groupKey']['id'] for group in groups_to_get]
|
||||
if usemember:
|
||||
try:
|
||||
groups_to_get = gapi.get_all_pages(ci.groups().memberships(),
|
||||
'searchTransitiveGroups',
|
||||
'memberships',
|
||||
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O],
|
||||
message_attribute=['groupKey', 'id'],
|
||||
page_message=page_message,
|
||||
parent='groups/-', query=usemember,
|
||||
pageSize=1000,
|
||||
fields='nextPageToken,memberships(groupKey(id),relationType)')
|
||||
except googleapiclient.errors.HttpError:
|
||||
controlflow.system_error_exit(
|
||||
2,
|
||||
f'enterprisemember requires Enterprise license')
|
||||
groups_to_get = [group['groupKey']['id'] for group in groups_to_get if group['relationType'] == 'DIRECT']
|
||||
else:
|
||||
groups_to_get = gapi.get_all_pages(
|
||||
ci.groups(),
|
||||
'list',
|
||||
'groups',
|
||||
message_attribute=['groupKey', 'id'],
|
||||
page_message=page_message,
|
||||
parent=parent,
|
||||
view='BASIC',
|
||||
pageSize=1000,
|
||||
fields='nextPageToken,groups(groupKey(id))')
|
||||
groups_to_get = [group['groupKey']['id'] for group in groups_to_get]
|
||||
i = 0
|
||||
count = len(groups_to_get)
|
||||
for group_email in groups_to_get:
|
||||
@@ -411,7 +462,7 @@ def update():
|
||||
|
||||
def _getRoleAndUsers():
|
||||
checkSuspended = None
|
||||
role = None
|
||||
role = ROLE_MEMBER
|
||||
expireTime = None
|
||||
i = 5
|
||||
if sys.argv[i].lower() in GROUP_ROLES_MAP:
|
||||
@@ -421,7 +472,10 @@ def update():
|
||||
checkSuspended = sys.argv[i].lower() == 'suspended'
|
||||
i += 1
|
||||
if sys.argv[i].lower() in ['expire', 'expires']:
|
||||
expireTime = sys.argv[i+1]
|
||||
if role != ROLE_MEMBER:
|
||||
controlflow.invalid_argument_exit(
|
||||
sys.argv[i], f'role {role}')
|
||||
expireTime = utils.get_time_or_delta_from_now(sys.argv[i+1])
|
||||
i += 2
|
||||
if sys.argv[i].lower() in usergroup_types:
|
||||
users_email = gam.getUsersToModify(entity_type=sys.argv[i].lower(),
|
||||
@@ -449,8 +503,6 @@ def update():
|
||||
return
|
||||
if myarg == 'add':
|
||||
role, expireTime, users_email = _getRoleAndUsers()
|
||||
if not role:
|
||||
role = ROLE_MEMBER
|
||||
if len(users_email) > 1:
|
||||
sys.stderr.write(
|
||||
f'Group: {group}, Will add {len(users_email)} {role}s.\n')
|
||||
@@ -473,7 +525,7 @@ def update():
|
||||
}
|
||||
if role != ROLE_MEMBER:
|
||||
body['roles'].append({'name': role})
|
||||
if expireTime:
|
||||
elif expireTime not in {None, NEVER_TIME}:
|
||||
for role in body['roles']:
|
||||
if role['name'] == ROLE_MEMBER:
|
||||
role['expiryDetail'] = {'expireTime': expireTime}
|
||||
@@ -544,7 +596,7 @@ def update():
|
||||
for user in to_add:
|
||||
item = ['gam', 'update', 'cigroup', f'id:{parent}', 'add',
|
||||
role,]
|
||||
if expireTime:
|
||||
if role == ROLE_MEMBER and expireTime not in {None, NEVER_TIME}:
|
||||
item.extend(['expires', expireTime])
|
||||
item.append(user)
|
||||
items.append(item)
|
||||
@@ -580,8 +632,6 @@ def update():
|
||||
)
|
||||
elif myarg == 'update':
|
||||
role, expireTime, users_email = _getRoleAndUsers()
|
||||
if not role:
|
||||
role = ROLE_MEMBER
|
||||
if len(users_email) > 1:
|
||||
sys.stderr.write(
|
||||
f'Group: {group}, Will update {len(users_email)} {role}s.\n'
|
||||
@@ -596,36 +646,48 @@ def update():
|
||||
items.append(item)
|
||||
elif len(users_email) > 0:
|
||||
name = membership_email_to_id(ci, parent, users_email[0])
|
||||
preUpdateRoles = []
|
||||
addRoles = []
|
||||
removeRoles = []
|
||||
current_roles = gapi.call(ci.groups().memberships(),
|
||||
'get',
|
||||
name=name,
|
||||
fields='roles').get('roles', [])
|
||||
current_roles = [role['name'] for role in current_roles]
|
||||
postUpdateRoles = []
|
||||
member_roles = gapi.call(ci.groups().memberships(),
|
||||
'get',
|
||||
name=name,
|
||||
fields='roles').get('roles', [{'name': ROLE_MEMBER}])
|
||||
current_roles = [crole['name'] for crole in member_roles]
|
||||
# When upgrading role, strip any expiryDetail from member before role changes
|
||||
if role != ROLE_MEMBER:
|
||||
for crole in member_roles:
|
||||
if 'expiryDetail' in crole:
|
||||
preUpdateRoles.append(
|
||||
{'fieldMask': 'expiryDetail.expireTime',
|
||||
'membershipRole': {'name': ROLE_MEMBER,
|
||||
'expiryDetail': {'expireTime': None}}})
|
||||
break
|
||||
# When downgrading role or simply updating member expireTime, update expiryDetail after role changes
|
||||
elif expireTime:
|
||||
postUpdateRoles.append(
|
||||
{'fieldMask': 'expiryDetail.expireTime',
|
||||
'membershipRole': {'name': role,
|
||||
'expiryDetail': {'expireTime': expireTime if expireTime != NEVER_TIME else None}}})
|
||||
for crole in current_roles:
|
||||
if crole not in {ROLE_MEMBER, role}:
|
||||
removeRoles.append(crole)
|
||||
if role not in current_roles:
|
||||
new_role = {'name': role}
|
||||
if role == ROLE_MEMBER and expireTime:
|
||||
if role == ROLE_MEMBER and expireTime not in {None, NEVER_TIME}:
|
||||
new_role['expiryDetail'] = {'expireTime': expireTime}
|
||||
expireTime = None
|
||||
postUpdateRoles = []
|
||||
addRoles.append(new_role)
|
||||
bodys = []
|
||||
if preUpdateRoles:
|
||||
bodys.append({'updateRolesParams': preUpdateRoles})
|
||||
if addRoles:
|
||||
bodys.append({'addRoles': addRoles})
|
||||
if removeRoles:
|
||||
bodys.append({'removeRoles': removeRoles})
|
||||
if expireTime:
|
||||
bodys.append({
|
||||
'name': ROLE_MEMBER,
|
||||
# Note this doesn't actually work for some reason. Only known method to change
|
||||
# expire time right now is to remove/re-add member.
|
||||
'expiryDetail': {
|
||||
'expireTime': expireTime
|
||||
}
|
||||
})
|
||||
if postUpdateRoles:
|
||||
bodys.append({'updateRolesParams': postUpdateRoles})
|
||||
for body in bodys:
|
||||
try:
|
||||
gapi.call(ci.groups().memberships(),
|
||||
|
||||
197
src/gam/gapi/cloudidentity/userinvitations.py
Normal file
197
src/gam/gapi/cloudidentity/userinvitations.py
Normal file
@@ -0,0 +1,197 @@
|
||||
"""Methods related to Cloud Identity User Invitation API"""
|
||||
import sys
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import googleapiclient
|
||||
|
||||
import gam
|
||||
from gam.var import GC_CUSTOMER_ID, GC_Values, MY_CUSTOMER, SORTORDER_CHOICES_MAP
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi import cloudidentity as gapi_cloudidentity
|
||||
|
||||
def _get_customerid():
|
||||
''' returns customer in "customers/(C){customer_id}' format needed for this API'''
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer != MY_CUSTOMER and customer[0] != 'C':
|
||||
customer = 'C' + customer
|
||||
return f'customers/{customer}'
|
||||
|
||||
def _reduce_name(name):
|
||||
''' converts long name into email address'''
|
||||
return name.split('/')[-1]
|
||||
|
||||
def _generic_action(action):
|
||||
'''generic function to call actionable APIs'''
|
||||
svc = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
customer = _get_customerid()
|
||||
email = sys.argv[3].lower()
|
||||
encoded_email = quote_plus(email)
|
||||
name = f'{customer}/userinvitations/{encoded_email}'
|
||||
action_map = {
|
||||
'cancel': 'Cancelling',
|
||||
'send': 'Sending'
|
||||
}
|
||||
print_action = action_map[action]
|
||||
print(f'{print_action} user invitation...')
|
||||
result = gapi.call(svc.customers().userinvitations(), action,
|
||||
name=name)
|
||||
name = result.get('response', {}).get('name')
|
||||
if name:
|
||||
result['response']['name'] = _reduce_name(name)
|
||||
display.print_json(result)
|
||||
|
||||
def _generic_get(get_type):
|
||||
'''generic function to call read data APIs'''
|
||||
svc = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
customer = _get_customerid()
|
||||
email = sys.argv[3].lower()
|
||||
encoded_email = quote_plus(email)
|
||||
name = f'{customer}/userinvitations/{encoded_email}'
|
||||
result = gapi.call(svc.customers().userinvitations(), get_type,
|
||||
name=name)
|
||||
if 'name' in result:
|
||||
result['name'] = _reduce_name(result['name'])
|
||||
display.print_json(result)
|
||||
|
||||
|
||||
# /batch is broken for Cloud Identity. Once fixed move this to using batch.
|
||||
# Current serial implementation will be SLOW...
|
||||
def bulk_is_invitable(emails):
|
||||
'''gam <users> check isinvitable'''
|
||||
def _invitation_result(request_id, response, _):
|
||||
if response.get('isInvitableUser'):
|
||||
rows.append({'invitableUsers': request_id})
|
||||
|
||||
svc = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
customer = _get_customerid()
|
||||
todrive = False
|
||||
#batch_size = 1000
|
||||
#ebatch = svc.new_batch_http_request(callback=_invitation_result)
|
||||
rows = []
|
||||
throw_reasons = [gapi_errors.ErrorReason.FOUR_O_THREE]
|
||||
for email in emails:
|
||||
encoded_email = quote_plus(email)
|
||||
name = f'{customer}/userinvitations/{encoded_email}'
|
||||
endpoint = svc.customers().userinvitations()
|
||||
#if len(ebatch._order) == batch_size:
|
||||
# ebatch.execute()
|
||||
# ebatch = svc.new_batch_http_request(callback=_invitation_result)
|
||||
#req = endpoint.isInvitableUser(name=name)
|
||||
#ebatch.add(req, request_id=email)
|
||||
try:
|
||||
result = gapi.call(endpoint,
|
||||
'isInvitableUser',
|
||||
throw_reasons=throw_reasons,
|
||||
name=name)
|
||||
except googleapiclient.errors.HttpError:
|
||||
continue
|
||||
if result.get('isInvitableUser'):
|
||||
rows.append({'invitableUsers': email})
|
||||
#ebatch.execute()
|
||||
titles = ['invitableUsers']
|
||||
display.write_csv_file(rows, titles, 'Invitable Users', todrive)
|
||||
|
||||
|
||||
def cancel():
|
||||
'''gam cancel userinvitation <email>'''
|
||||
_generic_action('cancel')
|
||||
|
||||
|
||||
def get():
|
||||
'''gam info userinvitation <email>'''
|
||||
_generic_get('get')
|
||||
|
||||
|
||||
def is_invitable_user():
|
||||
'''gam check userinvitation <email>'''
|
||||
_generic_get('isInvitableUser')
|
||||
|
||||
|
||||
def send():
|
||||
'''gam create userinvitation <email>'''
|
||||
_generic_action('send')
|
||||
|
||||
|
||||
USERINVITATION_ORDERBY_CHOICES_MAP = {
|
||||
'email': 'email',
|
||||
'updatetime': 'update_time',
|
||||
}
|
||||
|
||||
USERINVITATION_STATE_CHOICES_MAP = {
|
||||
'accepted': 'ACCEPTED',
|
||||
'declined': 'DECLINED',
|
||||
'invited': 'INVITED',
|
||||
'notyetsent': 'NOT_YET_SENT',
|
||||
}
|
||||
|
||||
def print_():
|
||||
'''gam print userinvitations'''
|
||||
svc = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
customer = _get_customerid()
|
||||
todrive = False
|
||||
titles = ['name', 'state', 'updateTime']
|
||||
rows = []
|
||||
filter_ = None
|
||||
orderByList = []
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'state':
|
||||
state = sys.argv[i + 1].lower().replace('_', '')
|
||||
if state in USERINVITATION_STATE_CHOICES_MAP:
|
||||
filter_ = f"state=='{USERINVITATION_STATE_CHOICES_MAP[state]}'"
|
||||
else:
|
||||
controlflow.expected_argument_exit('state',
|
||||
', '.join(USERINVITATION_STATE_CHOICES_MAP),
|
||||
state)
|
||||
i += 2
|
||||
elif myarg == 'orderby':
|
||||
fieldName = sys.argv[i + 1].lower()
|
||||
i += 2
|
||||
if fieldName in USERINVITATION_ORDERBY_CHOICES_MAP:
|
||||
fieldName = USERINVITATION_ORDERBY_CHOICES_MAP[fieldName]
|
||||
orderBy = ''
|
||||
if i < len(sys.argv):
|
||||
orderBy = sys.argv[i].lower()
|
||||
if orderBy in SORTORDER_CHOICES_MAP:
|
||||
orderBy = SORTORDER_CHOICES_MAP[orderBy]
|
||||
i += 1
|
||||
if orderBy != 'DESCENDING':
|
||||
orderByList.append(fieldName)
|
||||
else:
|
||||
orderByList.append(f'{fieldName} desc')
|
||||
else:
|
||||
controlflow.expected_argument_exit(
|
||||
'orderby', ', '.join(sorted(USERINVITATION_ORDERBY_CHOICES_MAP)),
|
||||
fieldName)
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam print userinvitations')
|
||||
if orderByList:
|
||||
orderBy = ' '.join(orderByList)
|
||||
else:
|
||||
orderBy = None
|
||||
gam.printGettingAllItems('User Invitations', filter_)
|
||||
page_message = gapi.got_total_items_msg('User Invitations', '...\n')
|
||||
invitations = gapi.get_all_pages(svc.customers().userinvitations(),
|
||||
'list',
|
||||
'userInvitations',
|
||||
page_message=page_message,
|
||||
parent=customer,
|
||||
filter=filter_,
|
||||
orderBy=orderBy)
|
||||
for invitation in invitations:
|
||||
invitation['name'] = _reduce_name(invitation['name'])
|
||||
row = {}
|
||||
for key, val in invitation.items():
|
||||
if key not in titles:
|
||||
titles.append(key)
|
||||
row[key] = val
|
||||
rows.append(row)
|
||||
display.write_csv_file(rows, titles, 'User Invitations', todrive)
|
||||
96
src/gam/gapi/contactdelegation.py
Normal file
96
src/gam/gapi/contactdelegation.py
Normal file
@@ -0,0 +1,96 @@
|
||||
"""Contact Delegation API calls"""
|
||||
|
||||
import sys
|
||||
|
||||
import gam
|
||||
from gam.gapi.directory import users as gapi_directory_users
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
|
||||
|
||||
def build():
|
||||
return gam.buildGAPIObject('contactdelegation')
|
||||
|
||||
|
||||
def create(users):
|
||||
condel = build()
|
||||
delegate = gam.normalizeEmailAddressOrUID(sys.argv[5])
|
||||
delegate = gapi_directory_users.get_primary(delegate)
|
||||
if not delegate:
|
||||
controlflow.system_error_exit(5,
|
||||
f'{sys.argv[5]} is not the primary address of a user.')
|
||||
body = {'email': delegate}
|
||||
i = 0
|
||||
count = len(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
print(
|
||||
f'Granting {delegate} contact delegate access to {user}{gam.currentCount(i, count)}'
|
||||
)
|
||||
gapi.call(condel.delegates(),
|
||||
'create',
|
||||
soft_errors=True,
|
||||
user=user,
|
||||
body=body)
|
||||
|
||||
|
||||
def delete(users):
|
||||
condel = build()
|
||||
delegate = gam.normalizeEmailAddressOrUID(sys.argv[5])
|
||||
delegate = gapi_directory_users.get_primary(delegate)
|
||||
if not delegate:
|
||||
controlflow.system_error_exit(5,
|
||||
f'{sys.argv[5]} is not the primary address of a user.')
|
||||
i = 0
|
||||
count = len(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
print(
|
||||
f'Deleting {delegate} contact delegate access to {user}{gam.currentCount(i, count)}'
|
||||
)
|
||||
gapi.call(condel.delegates(),
|
||||
'delete',
|
||||
soft_errors=True,
|
||||
user=user,
|
||||
delegate=delegate)
|
||||
|
||||
|
||||
def print_(users, csvFormat):
|
||||
condel = build()
|
||||
if csvFormat:
|
||||
todrive = False
|
||||
csv_rows = []
|
||||
titles = ['User', 'delegateAddress']
|
||||
else:
|
||||
csvStyle = False
|
||||
i = 5
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if not csvFormat and myarg == 'csv':
|
||||
csvStyle = True
|
||||
i += 1
|
||||
elif csvFormat and myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i],
|
||||
'gam print contactdelegation')
|
||||
page_message = gapi.got_total_items_msg('Contact Delegates', '...\n')
|
||||
for user in users:
|
||||
delegates = gapi.get_all_pages(condel.delegates(), 'list',
|
||||
'delegates',
|
||||
page_message=page_message,
|
||||
user=user)
|
||||
for delegate in delegates:
|
||||
if csvFormat:
|
||||
csv_rows.append({'User': user, 'delegateAddress': delegate['email']})
|
||||
else:
|
||||
if csvStyle:
|
||||
print(f'{user},{delegate["email"]}')
|
||||
else:
|
||||
print(
|
||||
f'Delegator: {user}\n Delegate Email: {delegate["email"]}\n'
|
||||
)
|
||||
if csvFormat:
|
||||
display.write_csv_file(csv_rows, titles, 'Contact Delegates', todrive)
|
||||
@@ -1,4 +1,7 @@
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
import googleapiclient
|
||||
@@ -15,11 +18,22 @@ from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
from gam import utils
|
||||
|
||||
|
||||
def _display_cros_command_result(cd, device_id, command_id, times_to_check_status):
|
||||
print(f'deviceId: {device_id}, commandId: {command_id}')
|
||||
final_states = {'EXPIRED', 'CANCELLED', 'EXECUTED_BY_CLIENT'}
|
||||
for _ in range(0, times_to_check_status):
|
||||
time.sleep(2)
|
||||
result = gapi.call(cd.customer().devices().chromeos().commands(), 'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id,
|
||||
commandId=command_id)
|
||||
display.print_json(result)
|
||||
if result.get('state') in final_states:
|
||||
return
|
||||
|
||||
def issue_command():
|
||||
cd = gapi_directory.build()
|
||||
i, devices = getCrOSDeviceEntity(3, cd)
|
||||
body = {}
|
||||
final_states = ['EXPIRED', 'CANCELLED', 'EXECUTED_BY_CLIENT']
|
||||
valid_commands = gapi.get_enum_values_minus_unspecified(
|
||||
cd._rootDesc['schemas']
|
||||
['DirectoryChromeosdevicesIssueCommandRequest']
|
||||
@@ -40,7 +54,7 @@ def issue_command():
|
||||
body['commandType'] = command_map[command]
|
||||
i += 2
|
||||
if command == 'setvolume':
|
||||
body['payload'] = {'volume': int(sys.argv[i])}
|
||||
body['payload'] = json.dumps({'volume': sys.argv[i]})
|
||||
i += 1
|
||||
elif myarg == 'timestocheckstatus':
|
||||
times_to_check_status = int(sys.argv[i+1])
|
||||
@@ -50,6 +64,8 @@ def issue_command():
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam issuecommand cros')
|
||||
if 'commandType' not in body:
|
||||
controlflow.missing_argument_exit('command <CrOSCommand>', 'gam issuecommand cros')
|
||||
if body['commandType'] == 'WIPE_USERS' and not doit:
|
||||
controlflow.system_error_exit(2, 'wipe_users command requires admin ' \
|
||||
'acknowledge user data will be destroyed with the ' \
|
||||
@@ -67,36 +83,28 @@ def issue_command():
|
||||
except googleapiclient.errors.HttpError:
|
||||
controlflow.system_error_exit(4, '400 response from Google. This ' \
|
||||
'usually indicates the devices was not in a state where it will' \
|
||||
' accept the command. For example, reboot and take_a_screenshot' \
|
||||
' accept the command. For example, reboot, set_volume and take_a_screenshot' \
|
||||
' require the device to be in auto-start kiosk app mode.')
|
||||
display.print_json(result)
|
||||
command_id = result.get('commandId')
|
||||
for i in range(0, times_to_check_status):
|
||||
time.sleep(2)
|
||||
result = gapi.call(cd.customer().devices().chromeos().commands(), 'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id,
|
||||
commandId=command_id)
|
||||
display.print_json(result)
|
||||
state = result.get('state')
|
||||
if state in final_states:
|
||||
break
|
||||
_display_cros_command_result(cd, device_id, command_id, times_to_check_status)
|
||||
|
||||
def get_command():
|
||||
cd = gapi_directory.build()
|
||||
i, devices = getCrOSDeviceEntity(3, cd)
|
||||
command_id = None
|
||||
times_to_check_status = 1
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'commandid':
|
||||
command_id = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'timestocheckstatus':
|
||||
times_to_check_status = int(sys.argv[i+1])
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam getcommand cros')
|
||||
for device_id in devices:
|
||||
result = gapi.call(cd.customer().devices().chromeos().commands(), 'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id,
|
||||
commandId=command_id)
|
||||
display.print_json(result)
|
||||
_display_cros_command_result(cd, device_id, command_id, times_to_check_status)
|
||||
|
||||
def doUpdateCros():
|
||||
cd = gapi_directory.build()
|
||||
@@ -388,9 +396,10 @@ def doGetCrosInfo():
|
||||
temp_label = tempInfo['label'].strip()
|
||||
temperature = tempInfo['temperature']
|
||||
print(f' {temp_label}: {temperature}')
|
||||
pct_info = cpuStatusReport['cpuUtilizationPercentageInfo']
|
||||
util = ','.join([str(x) for x in pct_info])
|
||||
print(f' cpuUtilizationPercentageInfo: {util}')
|
||||
if 'cpuUtilizationPercentageInfo' in cpuStatusReport:
|
||||
pct_info = cpuStatusReport['cpuUtilizationPercentageInfo']
|
||||
util = ','.join([str(x) for x in pct_info])
|
||||
print(f' cpuUtilizationPercentageInfo: {util}')
|
||||
diskVolumeReports = cros.get('diskVolumeReports', [])
|
||||
lenDVR = len(diskVolumeReports)
|
||||
if lenDVR:
|
||||
@@ -825,16 +834,16 @@ def doPrintCrosDevices():
|
||||
if i < lenCSR:
|
||||
nrow['cpuStatusReports.reportTime'] = \
|
||||
cpuStatusReports[i]['reportTime']
|
||||
tempInfos = cpuStatusReports[i].get('cpuTemperatureInfo',
|
||||
[])
|
||||
tempInfos = cpuStatusReports[i].get('cpuTemperatureInfo', [])
|
||||
for tempInfo in tempInfos:
|
||||
label = tempInfo['label'].strip()
|
||||
base = 'cpuStatusReports.cpuTemperatureInfo.'
|
||||
nrow[f'{base}{label}'] = tempInfo['temperature']
|
||||
cpu_field = 'cpuUtilizationPercentageInfo'
|
||||
cpu_reports = cpuStatusReports[i][cpu_field]
|
||||
cpu_pcts = [str(x) for x in cpu_reports]
|
||||
nrow[f'cpuStatusReports.{cpu_field}'] = ','.join(cpu_pcts)
|
||||
if cpu_field in cpuStatusReports[i]:
|
||||
cpu_reports = cpuStatusReports[i][cpu_field]
|
||||
cpu_pcts = [str(x) for x in cpu_reports]
|
||||
nrow[f'cpuStatusReports.{cpu_field}'] = ','.join(cpu_pcts)
|
||||
if i < lenDVR:
|
||||
volumeInfo = diskVolumeReports[i]['volumeInfo']
|
||||
j = 0
|
||||
|
||||
@@ -8,18 +8,25 @@ from gam.gapi import directory as gapi_directory
|
||||
from gam.gapi import reports as gapi_reports
|
||||
|
||||
|
||||
def _get_customerid():
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer != MY_CUSTOMER and customer[0] != 'C':
|
||||
customer = 'C' + customer
|
||||
return customer
|
||||
|
||||
def doGetCustomerInfo():
|
||||
cd = gapi_directory.build()
|
||||
customer_id = _get_customerid()
|
||||
customer_info = gapi.call(cd.customers(),
|
||||
'get',
|
||||
customerKey=GC_Values[GC_CUSTOMER_ID])
|
||||
customerKey=customer_id)
|
||||
print(f'Customer ID: {customer_info["id"]}')
|
||||
print(f'Primary Domain: {customer_info["customerDomain"]}')
|
||||
try:
|
||||
result = gapi.call(
|
||||
cd.domains(),
|
||||
'get',
|
||||
customer=customer_info['id'],
|
||||
customer=customer_id,
|
||||
domainName=customer_info['customerDomain'],
|
||||
fields='verified',
|
||||
throw_reasons=[gapi.errors.ErrorReason.DOMAIN_NOT_FOUND])
|
||||
@@ -35,7 +42,7 @@ def doGetCustomerInfo():
|
||||
domains = gapi.get_items(cd.domains(),
|
||||
'list',
|
||||
'domains',
|
||||
customer=GC_Values[GC_CUSTOMER_ID],
|
||||
customer=customer_id,
|
||||
fields='domains(creationTime)')
|
||||
for domain in domains:
|
||||
creation_timestamp = int(domain['creationTime']) / 1000
|
||||
@@ -67,9 +74,9 @@ def doGetCustomerInfo():
|
||||
}
|
||||
parameters = ','.join(list(user_counts_map))
|
||||
tryDate = datetime.date.today().strftime(YYYYMMDD_FORMAT)
|
||||
customerId = GC_Values[GC_CUSTOMER_ID]
|
||||
if customerId == MY_CUSTOMER:
|
||||
customerId = None
|
||||
reports_customer_id = customer_id
|
||||
if reports_customer_id == MY_CUSTOMER:
|
||||
reports_customer_id = None
|
||||
rep = gapi_reports.build()
|
||||
usage = None
|
||||
throw_reasons = [
|
||||
@@ -80,7 +87,7 @@ def doGetCustomerInfo():
|
||||
result = gapi.call(rep.customerUsageReports(),
|
||||
'get',
|
||||
throw_reasons=throw_reasons,
|
||||
customerId=customerId,
|
||||
customerId=reports_customer_id,
|
||||
date=tryDate,
|
||||
parameters=parameters)
|
||||
except gapi.errors.GapiInvalidError as e:
|
||||
@@ -111,6 +118,7 @@ def doGetCustomerInfo():
|
||||
def doUpdateCustomer():
|
||||
cd = gapi_directory.build()
|
||||
body = {}
|
||||
customer_id = _get_customerid()
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
@@ -136,14 +144,19 @@ def doUpdateCustomer():
|
||||
'update customer"')
|
||||
gapi.call(cd.customers(),
|
||||
'patch',
|
||||
customerKey=GC_Values[GC_CUSTOMER_ID],
|
||||
customerKey=customer_id,
|
||||
body=body)
|
||||
print('Updated customer')
|
||||
|
||||
|
||||
def setTrueCustomerId():
|
||||
if GC_Values[GC_CUSTOMER_ID] == MY_CUSTOMER:
|
||||
cd = gapi_directory.build()
|
||||
GC_Values[GC_CUSTOMER_ID] = gapi.call(cd.customers(), 'get',
|
||||
customerKey=GC_Values[GC_CUSTOMER_ID],
|
||||
fields='id').get('id', GC_Values[GC_CUSTOMER_ID])
|
||||
def setTrueCustomerId(cd=None):
|
||||
customer_id = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer_id == MY_CUSTOMER:
|
||||
if not cd:
|
||||
cd = gapi_directory.build()
|
||||
result = gapi.call(cd.customers(),
|
||||
'get',
|
||||
customerKey=customer_id,
|
||||
fields='id')
|
||||
GC_Values[GC_CUSTOMER_ID] = result.get('id',
|
||||
customer_id)
|
||||
|
||||
@@ -15,6 +15,10 @@ def GroupIsAbuseOrPostmaster(emailAddr):
|
||||
return emailAddr.startswith('abuse@') or emailAddr.startswith('postmaster@')
|
||||
|
||||
|
||||
def mapGroupEmailForSettings(emailAddr):
|
||||
return emailAddr.replace('/', '%2F')
|
||||
|
||||
|
||||
def create():
|
||||
cd = gapi_directory.build()
|
||||
body = {'email': gam.normalizeEmailAddressOrUID(sys.argv[3], noUid=True)}
|
||||
@@ -67,7 +71,7 @@ def create():
|
||||
gapi_errors.ErrorReason.SERVICE_LIMIT,
|
||||
gapi_errors.ErrorReason.NOT_FOUND
|
||||
],
|
||||
groupUniqueId=body['email'],
|
||||
groupUniqueId=mapGroupEmailForSettings(body['email']),
|
||||
fields='*')
|
||||
if current_settings is not None:
|
||||
gs_body = dict(
|
||||
@@ -75,7 +79,7 @@ def create():
|
||||
if gs_body:
|
||||
gapi.call(gs.groups(),
|
||||
'update',
|
||||
groupUniqueId=body['email'],
|
||||
groupUniqueId=mapGroupEmailForSettings(body['email']),
|
||||
retry_reasons=[
|
||||
gapi_errors.ErrorReason.SERVICE_LIMIT,
|
||||
gapi_errors.ErrorReason.NOT_FOUND
|
||||
@@ -170,7 +174,7 @@ def info(group_name=None):
|
||||
'get',
|
||||
throw_reasons=[gapi_errors.ErrorReason.AUTH_ERROR],
|
||||
retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
|
||||
groupUniqueId=basic_info['email']
|
||||
groupUniqueId=mapGroupEmailForSettings(basic_info['email'])
|
||||
) # Use email address retrieved from cd since GS API doesn't support uid
|
||||
if settings is None:
|
||||
settings = {}
|
||||
@@ -589,7 +593,7 @@ def print_():
|
||||
gapi_errors.ErrorReason.SERVICE_LIMIT,
|
||||
gapi_errors.ErrorReason.INVALID
|
||||
],
|
||||
groupUniqueId=groupEmail,
|
||||
groupUniqueId=mapGroupEmailForSettings(groupEmail),
|
||||
fields=gsfields)
|
||||
if settings:
|
||||
for key in settings:
|
||||
@@ -1174,7 +1178,7 @@ def update():
|
||||
gs.groups(),
|
||||
'get',
|
||||
retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
|
||||
groupUniqueId=group,
|
||||
groupUniqueId=mapGroupEmailForSettings(group),
|
||||
fields='*')
|
||||
if current_settings is not None:
|
||||
gs_body = dict(
|
||||
@@ -1185,7 +1189,7 @@ def update():
|
||||
gs.groups(),
|
||||
'update',
|
||||
retry_reasons=[gapi_errors.ErrorReason.SERVICE_LIMIT],
|
||||
groupUniqueId=group,
|
||||
groupUniqueId=mapGroupEmailForSettings(group),
|
||||
body=gs_body)
|
||||
print(f'updated group {group}')
|
||||
|
||||
|
||||
@@ -27,13 +27,13 @@ def info():
|
||||
'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID],
|
||||
resourceId=resourceId)
|
||||
if 'deviceId' in info:
|
||||
if 'deviceId' in device_info:
|
||||
device_info['deviceId'] = device_info['deviceId'].encode('unicode-escape').decode(
|
||||
UTF8)
|
||||
attrib = 'securityPatchLevel'
|
||||
if attrib in info and int(device_info[attrib]):
|
||||
if attrib in device_info and int(device_info[attrib]):
|
||||
device_info[attrib] = utils.formatTimestampYMDHMS(device_info[attrib])
|
||||
display.print_json(info)
|
||||
display.print_json(device_info)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -402,20 +402,18 @@ def getOrgUnitId(orgUnit, cd=None):
|
||||
return (orgUnit, result['orgUnitId'])
|
||||
|
||||
|
||||
def buildOrgUnitIdToNameMap():
|
||||
cd = gapi_directory.build()
|
||||
result = gapi.call(cd.orgunits(),
|
||||
'list',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID],
|
||||
fields='organizationUnits(orgUnitPath,orgUnitId)',
|
||||
type='all')
|
||||
GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME] = {}
|
||||
for orgUnit in result['organizationUnits']:
|
||||
GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME][
|
||||
orgUnit['orgUnitId']] = orgUnit['orgUnitPath']
|
||||
|
||||
|
||||
def orgunit_from_orgunitid(orgunitid):
|
||||
if not GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME]:
|
||||
buildOrgUnitIdToNameMap()
|
||||
return GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME].get(orgunitid, orgunitid)
|
||||
def orgunit_from_orgunitid(orgunitid, cd=None):
|
||||
if cd is None:
|
||||
cd = gapi_directory.build()
|
||||
orgunitpath = GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME].get(orgunitid)
|
||||
if not orgunitpath:
|
||||
try:
|
||||
orgunitpath = gapi.call(cd.orgunits(),
|
||||
'get',
|
||||
customerId=GC_Values[GC_CUSTOMER_ID],
|
||||
orgUnitPath=f'id:{orgunitid}' if not orgunitid.startswith('id:') else orgunitid,
|
||||
fields='orgUnitPath')['orgUnitPath']
|
||||
except:
|
||||
orgunitpath = orgunitid
|
||||
GM_Globals[GM_MAP_ORGUNIT_ID_TO_NAME][orgunitid] = orgunitpath
|
||||
return orgunitpath
|
||||
|
||||
187
src/gam/gapi/directory/printers.py
Normal file
187
src/gam/gapi/directory/printers.py
Normal file
@@ -0,0 +1,187 @@
|
||||
'''Commands to manage directory printers.'''
|
||||
# pylint: disable=unused-wildcard-import wildcard-import
|
||||
|
||||
import sys
|
||||
|
||||
import gam
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
from gam.var import *
|
||||
from gam.gapi import directory as gapi_directory
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
|
||||
|
||||
def _get_customerid():
|
||||
''' returns customer in "customers/C{customer}" format needed for this API'''
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer != MY_CUSTOMER and customer[0] != 'C':
|
||||
customer = 'C' + customer
|
||||
return f'customers/{customer}'
|
||||
|
||||
def _get_printer_attributes(i, cdapi=None):
|
||||
'''get printer attributes for create/update commands'''
|
||||
body = {}
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'description':
|
||||
body['description'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'displayname':
|
||||
body['displayName'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'makeandmodel':
|
||||
body['makeAndModel'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg in ['ou', 'org', 'orgunit', 'orgunitid']:
|
||||
_, body['orgUnitId'] = gapi_directory_orgunits.getOrgUnitId(sys.argv[i+1], cdapi)
|
||||
body['orgUnitId'] = body['orgUnitId'][3:]
|
||||
i += 2
|
||||
elif myarg == 'uri':
|
||||
body['uri'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg in {'driverless', 'usedriverlessconfig'}:
|
||||
body['useDriverlessConfig'] = True
|
||||
i += 1
|
||||
return body
|
||||
|
||||
|
||||
def create():
|
||||
'''gam create printer'''
|
||||
cdapi = gapi_directory.build()
|
||||
parent = _get_customerid()
|
||||
body = _get_printer_attributes(3, cdapi)
|
||||
result = gapi.call(cdapi.customers().chrome().printers(),
|
||||
'create',
|
||||
parent=parent,
|
||||
body=body)
|
||||
display.print_json(result)
|
||||
|
||||
|
||||
def delete():
|
||||
'''gam delete printer <PrinterIDList>|(file <FileName>)|(csvfile <FileName>:<FieldName>)'''
|
||||
cdapi = gapi_directory.build()
|
||||
customer_id = _get_customerid()
|
||||
printer_id = sys.argv[3]
|
||||
if printer_id.lower() not in {'file', 'csvfile'}:
|
||||
printer_ids = printer_id.replace(',', ' ').split()
|
||||
else:
|
||||
printer_ids = gam.getUsersToModify(f'cros{printer_id.lower()}', sys.argv[4])
|
||||
# max 50 per API call
|
||||
batch_size = 50
|
||||
for chunk in range(0, len(printer_ids), batch_size):
|
||||
body = {
|
||||
'printerIds': printer_ids[chunk:chunk + batch_size]
|
||||
}
|
||||
result = gapi.call(cdapi.customers().chrome().printers(),
|
||||
'batchDeletePrinters',
|
||||
parent=customer_id,
|
||||
body=body)
|
||||
for printer_id in result.get('printerIds', []):
|
||||
print(f'Deleted printer {printer_id}')
|
||||
for printer_id in result.get('failedPrinters', []):
|
||||
print(f'ERROR: failed to delete {printer_id.get("printerIds")}')
|
||||
|
||||
|
||||
def info():
|
||||
'''gam info printer'''
|
||||
cdapi = gapi_directory.build()
|
||||
customer = _get_customerid()
|
||||
printer_id = sys.argv[3]
|
||||
name = f'{customer}/chrome/printers/{printer_id}'
|
||||
printer = gapi.call(cdapi.customers().chrome().printers(),
|
||||
'get',
|
||||
name=name)
|
||||
if 'orgUnitId' in printer:
|
||||
printer['orgUnitPath'] = gapi_directory_orgunits.orgunit_from_orgunitid(
|
||||
printer['orgUnitId'], cdapi)
|
||||
display.print_json(printer)
|
||||
|
||||
|
||||
def print_():
|
||||
'''gam print printers'''
|
||||
cdapi = gapi_directory.build()
|
||||
parent = _get_customerid()
|
||||
filter_ = None
|
||||
todrive = False
|
||||
titles = []
|
||||
rows = []
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if myarg == 'filter':
|
||||
filter_ = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam print printermodels')
|
||||
printers = gapi.get_all_pages(cdapi.customers().chrome().printers(),
|
||||
'list',
|
||||
items='printers',
|
||||
parent=parent,
|
||||
filter=filter_)
|
||||
for printer in printers:
|
||||
if 'orgUnitId' in printer:
|
||||
printer['orgUnitPath'] = gapi_directory_orgunits.orgunit_from_orgunitid(
|
||||
printer['orgUnitId'], cdapi)
|
||||
row = {}
|
||||
for key, val in printer.items():
|
||||
if key not in titles:
|
||||
titles.append(key)
|
||||
row[key] = val
|
||||
rows.append(row)
|
||||
display.write_csv_file(rows, titles, 'Printers', todrive)
|
||||
|
||||
|
||||
def print_models():
|
||||
'''gam print printermodels'''
|
||||
cdapi = gapi_directory.build()
|
||||
parent = _get_customerid()
|
||||
filter_ = None
|
||||
todrive = False
|
||||
titles = []
|
||||
rows = []
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if myarg == 'filter':
|
||||
filter_ = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam print printermodels')
|
||||
models = gapi.get_all_pages(cdapi.customers().chrome().printers(),
|
||||
'listPrinterModels',
|
||||
items='printerModels',
|
||||
parent=parent,
|
||||
pageSize=10000,
|
||||
filter=filter_)
|
||||
for model in models:
|
||||
row = {}
|
||||
for key, val in model.items():
|
||||
if key not in titles:
|
||||
titles.append(key)
|
||||
row[key] = val
|
||||
rows.append(row)
|
||||
display.write_csv_file(rows, titles, 'Printer Models', todrive)
|
||||
|
||||
|
||||
def update():
|
||||
'''gam update printer'''
|
||||
cdapi = gapi_directory.build()
|
||||
customer = _get_customerid()
|
||||
printer_id = sys.argv[3]
|
||||
name = f'{customer}/chrome/printers/{printer_id}'
|
||||
body = _get_printer_attributes(4, cdapi)
|
||||
update_mask = ','.join(body)
|
||||
# note clearMask seems unnecessary. Updating field to '' clears it.
|
||||
result = gapi.call(cdapi.customers().chrome().printers(),
|
||||
'patch',
|
||||
name=name,
|
||||
updateMask=update_mask,
|
||||
body=body)
|
||||
display.print_json(result)
|
||||
@@ -1,7 +1,22 @@
|
||||
from time import sleep
|
||||
|
||||
import gam
|
||||
from gam import gapi
|
||||
from gam.gapi import directory as gapi_directory
|
||||
|
||||
|
||||
def get_primary(email):
|
||||
'''returns primary email of user or empty if email is not a user primary or
|
||||
alias address.'''
|
||||
cd = gapi_directory.build()
|
||||
result = gapi.call(cd.users(), 'get', userKey=email,
|
||||
projection='basic', fields='primaryEmail',
|
||||
soft_errors=True)
|
||||
if not result:
|
||||
return ''
|
||||
return result.get('primaryEmail', '').lower()
|
||||
|
||||
|
||||
def signout(users):
|
||||
cd = gapi_directory.build()
|
||||
i = 0
|
||||
@@ -28,3 +43,22 @@ def turn_off_2sv(users):
|
||||
'turnOff',
|
||||
soft_errors=True,
|
||||
userKey=user)
|
||||
|
||||
def wait_for_mailbox(users):
|
||||
'''Wait until users mailbox is provisioned.'''
|
||||
cd = gapi_directory.build()
|
||||
i = 0
|
||||
count = len(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user = gam.normalizeEmailAddressOrUID(user)
|
||||
while True:
|
||||
result = gapi.call(cd.users(),
|
||||
'get',
|
||||
'fields=isMailboxSetup',
|
||||
userKey=user)
|
||||
mailbox_is_setup = result.get('isMailboxSetup')
|
||||
print(f'{user} mailboxIsSetup: {mailbox_is_setup}')
|
||||
if mailbox_is_setup:
|
||||
break
|
||||
sleep(3)
|
||||
|
||||
@@ -116,6 +116,7 @@ class ErrorReason(Enum):
|
||||
DUPLICATE = 'duplicate'
|
||||
FAILED_PRECONDITION = 'failedPrecondition'
|
||||
FORBIDDEN = 'forbidden'
|
||||
FIVE_O_THREE = '503'
|
||||
FOUR_O_NINE = '409'
|
||||
FOUR_O_O = '400'
|
||||
FOUR_O_THREE = '403'
|
||||
@@ -153,6 +154,7 @@ DEFAULT_RETRY_REASONS = [
|
||||
ErrorReason.GATEWAY_TIMEOUT,
|
||||
ErrorReason.INTERNAL_ERROR,
|
||||
ErrorReason.FOUR_TWO_NINE,
|
||||
ErrorReason.FIVE_O_THREE,
|
||||
]
|
||||
GMAIL_THROW_REASONS = [ErrorReason.SERVICE_NOT_AVAILABLE]
|
||||
GROUP_GET_THROW_REASONS = [
|
||||
|
||||
@@ -7,8 +7,17 @@ from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi.directory import customer as gapi_directory_customer
|
||||
|
||||
|
||||
def _get_customerid():
|
||||
''' returns customerId with format C{customer_id}'''
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
customer_id = GC_Values[GC_CUSTOMER_ID]
|
||||
if customer_id[0] != 'C':
|
||||
customer_id = 'C' + customer_id
|
||||
return customer_id
|
||||
|
||||
def build():
|
||||
return gam.buildGAPIObject('licensing')
|
||||
|
||||
@@ -127,6 +136,7 @@ def print_(returnFields=None,
|
||||
countsOnly=False,
|
||||
returnCounts=False):
|
||||
lic = build()
|
||||
customer_id = _get_customerid()
|
||||
products = []
|
||||
licenses = []
|
||||
licenseCounts = []
|
||||
@@ -193,7 +203,7 @@ def print_(returnFields=None,
|
||||
gapi_errors.ErrorReason.FORBIDDEN
|
||||
],
|
||||
page_message=page_message,
|
||||
customerId=GC_Values[GC_DOMAIN],
|
||||
customerId=customer_id,
|
||||
productId=product,
|
||||
skuId=sku,
|
||||
fields=fields)
|
||||
@@ -223,7 +233,7 @@ def print_(returnFields=None,
|
||||
gapi_errors.ErrorReason.FORBIDDEN
|
||||
],
|
||||
page_message=page_message,
|
||||
customerId=GC_Values[GC_DOMAIN],
|
||||
customerId=customer_id,
|
||||
productId=productId,
|
||||
fields=fields)
|
||||
if countsOnly:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import datetime
|
||||
import json
|
||||
import sys
|
||||
from time import sleep
|
||||
|
||||
import googleapiclient.http
|
||||
|
||||
@@ -11,6 +12,7 @@ from gam import display
|
||||
from gam import fileutils
|
||||
from gam import gapi
|
||||
from gam.gapi import storage as gapi_storage
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
from gam import utils
|
||||
|
||||
|
||||
@@ -84,21 +86,120 @@ VAULT_SEARCH_METHODS_MAP = {
|
||||
VAULT_SEARCH_METHODS_LIST = [
|
||||
'accounts', 'orgunit', 'shareddrives', 'rooms', 'everyone'
|
||||
]
|
||||
QUERY_ARGS = ['corpus', 'scope', 'terms', 'start', 'starttime',
|
||||
'end', 'endtime', 'timezone', 'excludedrafts',
|
||||
'driveversiondate', 'includeshareddrives', 'includeteamdrives',
|
||||
'includerooms'] + list(VAULT_SEARCH_METHODS_MAP.keys())
|
||||
|
||||
def _build_query(query, myarg, i, query_discovery):
|
||||
if not query:
|
||||
query = {'dataScope': 'ALL_DATA'}
|
||||
if myarg == 'corpus':
|
||||
query['corpus'] = sys.argv[i + 1].upper()
|
||||
allowed_corpuses = gapi.get_enum_values_minus_unspecified(
|
||||
query_discovery['properties']['corpus']['enum'])
|
||||
if query['corpus'] not in allowed_corpuses:
|
||||
controlflow.expected_argument_exit('corpus',
|
||||
', '.join(allowed_corpuses),
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in VAULT_SEARCH_METHODS_MAP:
|
||||
if query.get('searchMethod'):
|
||||
message = f'Multiple search methods ' \
|
||||
f'({", ".join(VAULT_SEARCH_METHODS_LIST)})' \
|
||||
f'specified, only one is allowed'
|
||||
controlflow.system_error_exit(3, message)
|
||||
searchMethod = VAULT_SEARCH_METHODS_MAP[myarg]
|
||||
query['searchMethod'] = searchMethod
|
||||
if searchMethod == 'ACCOUNT':
|
||||
query['accountInfo'] = {
|
||||
'emails': sys.argv[i + 1].split(',')
|
||||
}
|
||||
i += 2
|
||||
elif searchMethod == 'ORG_UNIT':
|
||||
query['orgUnitInfo'] = {
|
||||
'orgUnitId': gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])[1]
|
||||
}
|
||||
i += 2
|
||||
elif searchMethod == 'SHARED_DRIVE':
|
||||
query['sharedDriveInfo'] = {
|
||||
'sharedDriveIds': sys.argv[i + 1].split(',')
|
||||
}
|
||||
i += 2
|
||||
elif searchMethod == 'ROOM':
|
||||
query['hangoutsChatInfo'] = {
|
||||
'roomId': sys.argv[i + 1].split(',')
|
||||
}
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
elif myarg == 'scope':
|
||||
query['dataScope'] = sys.argv[i + 1].upper()
|
||||
allowed_scopes = gapi.get_enum_values_minus_unspecified(
|
||||
query_discovery['properties']['dataScope']['enum'])
|
||||
if query['dataScope'] not in allowed_scopes:
|
||||
controlflow.expected_argument_exit('scope',
|
||||
', '.join(allowed_scopes),
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['terms']:
|
||||
query['terms'] = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg in ['start', 'starttime']:
|
||||
query['startTime'] = utils.get_date_zero_time_or_full_time(
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['end', 'endtime']:
|
||||
query['endTime'] = utils.get_date_zero_time_or_full_time(
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['timezone']:
|
||||
query['timeZone'] = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg in ['excludedrafts']:
|
||||
query['mailOptions'] = {
|
||||
'excludeDrafts': gam.getBoolean(sys.argv[i + 1], myarg)
|
||||
}
|
||||
i += 2
|
||||
elif myarg in ['driveversiondate']:
|
||||
query.setdefault('driveOptions', {})['versionDate'] = \
|
||||
utils.get_date_zero_time_or_full_time(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg in ['includeshareddrives', 'includeteamdrives']:
|
||||
query.setdefault(
|
||||
'driveOptions', {})['includeSharedDrives'] = gam.getBoolean(
|
||||
sys.argv[i + 1], myarg)
|
||||
i += 2
|
||||
elif myarg in ['includerooms']:
|
||||
query['hangoutsChatOptions'] = {
|
||||
'includeRooms': gam.getBoolean(sys.argv[i + 1], myarg)
|
||||
}
|
||||
i += 2
|
||||
return (query, i)
|
||||
|
||||
def _validate_query(query, query_discovery):
|
||||
if 'corpus' not in query:
|
||||
allowed_corpuses = gapi.get_enum_values_minus_unspecified(
|
||||
query_discovery['properties']['corpus']['enum'])
|
||||
controlflow.system_error_exit(3, 'you must specify a corpus. ' \
|
||||
f'Choose one of {", ".join(allowed_corpuses)}')
|
||||
if 'searchMethod' not in query:
|
||||
controlflow.system_error_exit(3, f'you must specify a search method. ' \
|
||||
'Choose one of ' \
|
||||
f'{", ".join(VAULT_SEARCH_METHODS_LIST)}')
|
||||
|
||||
|
||||
def createExport():
|
||||
v = buildGAPIObject()
|
||||
allowed_corpuses = gapi.get_enum_values_minus_unspecified(
|
||||
v._rootDesc['schemas']['Query']['properties']['corpus']['enum'])
|
||||
allowed_scopes = gapi.get_enum_values_minus_unspecified(
|
||||
v._rootDesc['schemas']['Query']['properties']['dataScope']['enum'])
|
||||
query_discovery = v._rootDesc['schemas']['Query']
|
||||
allowed_formats = gapi.get_enum_values_minus_unspecified(
|
||||
v._rootDesc['schemas']['MailExportOptions']['properties']
|
||||
['exportFormat']['enum'])
|
||||
export_format = 'MBOX'
|
||||
showConfidentialModeContent = None # default to not even set
|
||||
matterId = None
|
||||
body = {'query': {'dataScope': 'ALL_DATA'}, 'exportOptions': {}}
|
||||
query = None
|
||||
body = {'exportOptions': {}}
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
@@ -109,83 +210,8 @@ def createExport():
|
||||
elif myarg == 'name':
|
||||
body['name'] = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg == 'corpus':
|
||||
body['query']['corpus'] = sys.argv[i + 1].upper()
|
||||
if body['query']['corpus'] not in allowed_corpuses:
|
||||
controlflow.expected_argument_exit('corpus',
|
||||
', '.join(allowed_corpuses),
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in VAULT_SEARCH_METHODS_MAP:
|
||||
if body['query'].get('searchMethod'):
|
||||
message = f'Multiple search methods ' \
|
||||
f'({", ".join(VAULT_SEARCH_METHODS_LIST)})' \
|
||||
f'specified, only one is allowed'
|
||||
controlflow.system_error_exit(3, message)
|
||||
searchMethod = VAULT_SEARCH_METHODS_MAP[myarg]
|
||||
body['query']['searchMethod'] = searchMethod
|
||||
if searchMethod == 'ACCOUNT':
|
||||
body['query']['accountInfo'] = {
|
||||
'emails': sys.argv[i + 1].split(',')
|
||||
}
|
||||
i += 2
|
||||
elif searchMethod == 'ORG_UNIT':
|
||||
body['query']['orgUnitInfo'] = {
|
||||
'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
|
||||
}
|
||||
i += 2
|
||||
elif searchMethod == 'SHARED_DRIVE':
|
||||
body['query']['sharedDriveInfo'] = {
|
||||
'sharedDriveIds': sys.argv[i + 1].split(',')
|
||||
}
|
||||
i += 2
|
||||
elif searchMethod == 'ROOM':
|
||||
body['query']['hangoutsChatInfo'] = {
|
||||
'roomId': sys.argv[i + 1].split(',')
|
||||
}
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
elif myarg == 'scope':
|
||||
body['query']['dataScope'] = sys.argv[i + 1].upper()
|
||||
if body['query']['dataScope'] not in allowed_scopes:
|
||||
controlflow.expected_argument_exit('scope',
|
||||
', '.join(allowed_scopes),
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['terms']:
|
||||
body['query']['terms'] = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg in ['start', 'starttime']:
|
||||
body['query']['startTime'] = utils.get_date_zero_time_or_full_time(
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['end', 'endtime']:
|
||||
body['query']['endTime'] = utils.get_date_zero_time_or_full_time(
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg in ['timezone']:
|
||||
body['query']['timeZone'] = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg in ['excludedrafts']:
|
||||
body['query']['mailOptions'] = {
|
||||
'excludeDrafts': gam.getBoolean(sys.argv[i + 1], myarg)
|
||||
}
|
||||
i += 2
|
||||
elif myarg in ['driveversiondate']:
|
||||
body['query'].setdefault('driveOptions', {})['versionDate'] = \
|
||||
utils.get_date_zero_time_or_full_time(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg in ['includeshareddrives', 'includeteamdrives']:
|
||||
body['query'].setdefault(
|
||||
'driveOptions', {})['includeSharedDrives'] = gam.getBoolean(
|
||||
sys.argv[i + 1], myarg)
|
||||
i += 2
|
||||
elif myarg in ['includerooms']:
|
||||
body['query']['hangoutsChatOptions'] = {
|
||||
'includeRooms': gam.getBoolean(sys.argv[i + 1], myarg)
|
||||
}
|
||||
i += 2
|
||||
elif myarg in QUERY_ARGS:
|
||||
query, i = _build_query(query, myarg, i, query_discovery)
|
||||
elif myarg in ['format']:
|
||||
export_format = sys.argv[i + 1].upper()
|
||||
if export_format not in allowed_formats:
|
||||
@@ -216,13 +242,8 @@ def createExport():
|
||||
if not matterId:
|
||||
controlflow.system_error_exit(
|
||||
3, 'you must specify a matter for the new export.')
|
||||
if 'corpus' not in body['query']:
|
||||
controlflow.system_error_exit(3, f'you must specify a corpus for the ' \
|
||||
f'new export. Choose one of {", ".join(allowed_corpuses)}')
|
||||
if 'searchMethod' not in body['query']:
|
||||
controlflow.system_error_exit(3, f'you must specify a search method ' \
|
||||
'for the new export. Choose one of ' \
|
||||
f'{", ".join(VAULT_SEARCH_METHODS_LIST)}')
|
||||
_validate_query(query, query_discovery)
|
||||
body['query'] = query
|
||||
if 'name' not in body:
|
||||
corpus_name = body['query']['corpus']
|
||||
corpus_date = datetime.datetime.now()
|
||||
@@ -270,6 +291,81 @@ def getExportInfo():
|
||||
display.print_json(export)
|
||||
|
||||
|
||||
def print_count():
|
||||
v = buildGAPIObject()
|
||||
query_discovery = v._rootDesc['schemas']['Query']
|
||||
matterId = None
|
||||
operation_wait = 15
|
||||
query = None
|
||||
body = {'view': 'ALL'}
|
||||
name = None
|
||||
todrive = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'matter':
|
||||
matterId = getMatterItem(v, sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg == 'operation':
|
||||
name = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg in QUERY_ARGS:
|
||||
query, i = _build_query(query, myarg, i, query_discovery)
|
||||
elif myarg == 'wait':
|
||||
operation_wait = int(sys.argv[i + 1])
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam create export')
|
||||
if not matterId:
|
||||
controlflow.system_error_exit(
|
||||
3, 'you must specify a matter for the count.')
|
||||
if name:
|
||||
operation = {'name': name}
|
||||
else:
|
||||
_validate_query(query, query_discovery)
|
||||
body['query'] = query
|
||||
operation = gapi.call(v.matters(), 'count', matterId=matterId, body=body)
|
||||
print(f'Watching operation {operation["name"]}...')
|
||||
while not operation.get('done'):
|
||||
print(f' operation {operation["name"]} is not done yet. Checking again in {operation_wait} seconds')
|
||||
sleep(operation_wait)
|
||||
operation = gapi.call(v.operations(), 'get', name=operation['name'])
|
||||
response = operation.get('response', {})
|
||||
query = operation['metadata']['query']
|
||||
search_method = query.get('searchMethod')
|
||||
# ARGH count results don't include accounts with zero items.
|
||||
# so we keep track of which accounts we searched and can report
|
||||
# zero data for them.
|
||||
if search_method == 'ACCOUNT':
|
||||
query_accounts = query.get('accountInfo', [])
|
||||
elif search_method == 'ENTIRE_ORG':
|
||||
query_accounts = gam.getUsersToModify('all', 'users')
|
||||
elif search_method == 'ORG_UNIT':
|
||||
org_unit = query['orgUnitInfo']['orgUnitId']
|
||||
query_accounts = gam.getUsersToModify('ou', org_unit)
|
||||
mailcounts = response.get('mailCountResult', {})
|
||||
groupcounts = response.get('groupsCountResult', {})
|
||||
csv_rows = []
|
||||
for a_count in [mailcounts, groupcounts]:
|
||||
for errored_account in a_count.get('accountCountErrors', []):
|
||||
account = errored_account.get('account')
|
||||
csv_rows.append({'account': account, 'error': errored_account.get('errorType')})
|
||||
if account in query_accounts: query_accounts.remove(account)
|
||||
for account in a_count.get('nonQueryableAccounts', []):
|
||||
csv_rows.append({'account': account, 'error': 'Not queried because not on hold'})
|
||||
if account in query_accounts: query_accounts.remove(account)
|
||||
for account in a_count.get('accountCounts', []):
|
||||
email = account.get('account', {}).get('email', '')
|
||||
csv_rows.append({'account': email, 'count': account.get('count')})
|
||||
if email in query_accounts: query_accounts.remove(email)
|
||||
for account in query_accounts:
|
||||
csv_rows.append({'account': account, 'count': 0})
|
||||
titles = ['account', 'count', 'error']
|
||||
display.write_csv_file(csv_rows, titles, 'Vault Counts', todrive)
|
||||
|
||||
def createHold():
|
||||
v = buildGAPIObject()
|
||||
allowed_corpuses = gapi.get_enum_values_minus_unspecified(
|
||||
@@ -301,7 +397,7 @@ def createHold():
|
||||
i += 2
|
||||
elif myarg in ['orgunit', 'ou']:
|
||||
body['orgUnit'] = {
|
||||
'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
|
||||
'orgUnitId': gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])[1]
|
||||
}
|
||||
i += 2
|
||||
elif myarg in ['start', 'starttime']:
|
||||
@@ -407,7 +503,7 @@ def getHoldInfo():
|
||||
acct_email = gam.convertUIDtoEmailAddress(uid, cd, [account_type])
|
||||
results['accounts'][i]['email'] = acct_email
|
||||
if 'orgUnit' in results:
|
||||
results['orgUnit']['orgUnitPath'] = gam.doGetOrgInfo(
|
||||
results['orgUnit']['orgUnitPath'] = gapi_directory_orgunits.info(
|
||||
results['orgUnit']['orgUnitId'], return_attrib='orgUnitPath')
|
||||
display.print_json(results)
|
||||
|
||||
@@ -496,7 +592,7 @@ def updateHold():
|
||||
i += 2
|
||||
elif myarg in ['orgunit', 'ou']:
|
||||
body['orgUnit'] = {
|
||||
'orgUnitId': gam.getOrgUnitId(sys.argv[i + 1])[1]
|
||||
'orgUnitId': gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])[1]
|
||||
}
|
||||
i += 2
|
||||
elif myarg in ['start', 'starttime']:
|
||||
|
||||
@@ -59,6 +59,16 @@ class _DeHTMLParser(HTMLParser):
|
||||
re.sub(r'\n +', '\n', ''.join(self.__text))).strip()
|
||||
|
||||
|
||||
def commonprefix(m):
|
||||
'''Given a list of strings m, return string which is prefix common to all'''
|
||||
s1 = min(m)
|
||||
s2 = max(m)
|
||||
for i, c in enumerate(s1):
|
||||
if c != s2[i]:
|
||||
return s1[:i]
|
||||
return s1
|
||||
|
||||
|
||||
def dehtml(text):
|
||||
try:
|
||||
parser = _DeHTMLParser()
|
||||
@@ -228,12 +238,14 @@ def get_yyyymmdd(argstr, minLen=1, returnTimeStamp=False, returnDateTime=False):
|
||||
def get_time_or_delta_from_now(time_string):
|
||||
"""Get an ISO 8601 time or a positive/negative delta applied to now.
|
||||
Args:
|
||||
time_string (string): The time or delta (e.g. '2017-09-01T12:34:56Z' or '-4h')
|
||||
time_string (string): The time or delta (e.g. '2017-09-01T12:34:56Z' or '-4h') or never
|
||||
Returns:
|
||||
string: iso8601 formatted datetime in UTC.
|
||||
"""
|
||||
time_string = time_string.strip().upper()
|
||||
if time_string:
|
||||
if time_string == 'NEVER':
|
||||
return NEVER_TIME
|
||||
if time_string[0] not in ['+', '-']:
|
||||
return time_string
|
||||
return (datetime.datetime.utcnow() +
|
||||
|
||||
@@ -8,7 +8,7 @@ import platform
|
||||
import re
|
||||
|
||||
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
|
||||
GAM_VERSION = '5.24'
|
||||
GAM_VERSION = '6.00'
|
||||
GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
GAM_URL = 'https://git.io/gam'
|
||||
@@ -59,12 +59,42 @@ SKUS = {
|
||||
'1010310002': {
|
||||
'product': '101031',
|
||||
'aliases': ['gsefe', 'e4e', 'gsuiteenterpriseeducation'],
|
||||
'displayName': 'G Suite Enterprise for Education'
|
||||
'displayName': 'Google Workspace for Education Plus - Legacy'
|
||||
},
|
||||
'1010310003': {
|
||||
'product': '101031',
|
||||
'aliases': ['gsefes', 'e4es', 'gsuiteenterpriseeducationstudent'],
|
||||
'displayName': 'G Suite Enterprise for Education (Student)'
|
||||
'displayName': 'Google Workspace for Education Plus - Legacy (Student)'
|
||||
},
|
||||
'1010310005': {
|
||||
'product': '101031',
|
||||
'aliases': ['gwes', 'workspaceeducationstandard'],
|
||||
'displayName': 'Google Workspace for Education Standard'
|
||||
},
|
||||
'1010310006': {
|
||||
'product': '101031',
|
||||
'aliases': ['gwesstaff', 'workspaceeducationstandardstaff'],
|
||||
'displayName': 'Google Workspace for Education Standard (Staff)'
|
||||
},
|
||||
'1010310007': {
|
||||
'product': '101031',
|
||||
'aliases': ['gwesstudent', 'workspaceeducationstandardstudent'],
|
||||
'displayName': 'Google Workspace for Education Standard (Extra Student)'
|
||||
},
|
||||
'1010310008': {
|
||||
'product': '101031',
|
||||
'aliases': ['gwep', 'workspaceeducationplus'],
|
||||
'displayName': 'Google Workspace for Education Plus'
|
||||
},
|
||||
'1010310009': {
|
||||
'product': '101031',
|
||||
'aliases': ['gwepstaff', 'workspaceeducationplusstaff'],
|
||||
'displayName': 'Google Workspace for Education Plus (Staff)'
|
||||
},
|
||||
'1010310010': {
|
||||
'product': '101031',
|
||||
'aliases': ['gwepstudent', 'workspaceeducationplusstudent'],
|
||||
'displayName': 'Google Workspace for Education Plus (Extra Student)'
|
||||
},
|
||||
'1010330003': {
|
||||
'product': '101033',
|
||||
@@ -81,6 +111,11 @@ SKUS = {
|
||||
'aliases': ['gvpremier', 'voicepremier', 'googlevoicepremier'],
|
||||
'displayName': 'Google Voice Premier'
|
||||
},
|
||||
'1010370001': {
|
||||
'product': '101037',
|
||||
'aliases': ['gwetlu', 'workspaceeducationupgrade'],
|
||||
'displayName': 'Google Workspace for Education: Teaching and Learning Upgrade'
|
||||
},
|
||||
'Google-Apps': {
|
||||
'product': 'Google-Apps',
|
||||
'aliases': ['standard', 'free'],
|
||||
@@ -228,12 +263,12 @@ SKUS = {
|
||||
PRODUCTID_NAME_MAPPINGS = {
|
||||
'101001': 'Cloud Identity Free',
|
||||
'101005': 'Cloud Identity Premium',
|
||||
'101031': 'G Suite Enterprise for Education',
|
||||
'101031': 'G Suite Workspace for Education',
|
||||
'101033': 'Google Voice',
|
||||
'101034': 'G Suite Archived',
|
||||
'101037': 'G Suite Workspace for Education',
|
||||
'Google-Apps': 'Google Workspace',
|
||||
'Google-Chrome-Device-Management': 'Google Chrome Device Management',
|
||||
'Google-Coordinate': 'Google Coordinate',
|
||||
'Google-Drive-storage': 'Google Drive Storage',
|
||||
'Google-Vault': 'Google Vault',
|
||||
}
|
||||
@@ -241,7 +276,6 @@ PRODUCTID_NAME_MAPPINGS = {
|
||||
# Legacy APIs that use v1 discovery. Newer APIs should all use v2.
|
||||
V1_DISCOVERY_APIS = {
|
||||
'admin',
|
||||
'appsactivity',
|
||||
'calendar',
|
||||
'drive',
|
||||
'oauth2',
|
||||
@@ -260,13 +294,16 @@ API_NAME_MAPPING = {
|
||||
|
||||
API_VER_MAPPING = {
|
||||
'alertcenter': 'v1beta1',
|
||||
'appsactivity': 'v1',
|
||||
'driveactivity': 'v2',
|
||||
'calendar': 'v3',
|
||||
'cbcm': 'v1.1beta1',
|
||||
'chromepolicy': 'v1',
|
||||
'classroom': 'v1',
|
||||
'cloudidentity': 'v1',
|
||||
'cloudidentity_beta': 'v1beta1',
|
||||
'cloudresourcemanager': 'v2',
|
||||
'cloudresourcemanagerv1': 'v1',
|
||||
'contactdelegation': 'v1',
|
||||
'datatransfer': 'datatransfer_v1',
|
||||
'directory': 'directory_v1',
|
||||
'drive': 'v2',
|
||||
@@ -292,12 +329,12 @@ USERINFO_EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
|
||||
|
||||
API_SCOPE_MAPPING = {
|
||||
'alertcenter': ['https://www.googleapis.com/auth/apps.alerts',],
|
||||
'appsactivity': [
|
||||
'https://www.googleapis.com/auth/activity',
|
||||
'driveactivity': [
|
||||
'https://www.googleapis.com/auth/drive.activity',
|
||||
'https://www.googleapis.com/auth/drive',
|
||||
],
|
||||
'calendar': ['https://www.googleapis.com/auth/calendar',],
|
||||
'cloudidentity': ['https://www.googleapis.com/auth/cloud-identity',],
|
||||
'cloudidentity': ['https://www.googleapis.com/auth/cloud-identity'],
|
||||
'drive': ['https://www.googleapis.com/auth/drive',],
|
||||
'drive3': ['https://www.googleapis.com/auth/drive',],
|
||||
'gmail': [
|
||||
@@ -906,6 +943,7 @@ CROS_ARGUMENT_TO_PROPERTY_MAP = {
|
||||
'ethernetmacaddress0': ['ethernetMacAddress0',],
|
||||
'firmwareversion': ['firmwareVersion',],
|
||||
'lastenrollmenttime': ['lastEnrollmentTime',],
|
||||
'lastknownnetwork': ['lastKnownNetwork'],
|
||||
'lastsync': ['lastSync',],
|
||||
'location': ['annotatedLocation',],
|
||||
'macaddress': ['macAddress',],
|
||||
@@ -1142,7 +1180,7 @@ GM_Globals = {
|
||||
GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID: None,
|
||||
GM_ENABLEDASA_TXT: '',
|
||||
GM_LAST_UPDATE_CHECK_TXT: '',
|
||||
GM_MAP_ORGUNIT_ID_TO_NAME: None,
|
||||
GM_MAP_ORGUNIT_ID_TO_NAME: {},
|
||||
GM_MAP_ROLE_ID_TO_NAME: None,
|
||||
GM_MAP_ROLE_NAME_TO_ID: None,
|
||||
GM_MAP_USER_ID_TO_NAME: None,
|
||||
@@ -1220,6 +1258,8 @@ GC_CSV_HEADER_FILTER = 'csv_header_filter'
|
||||
GC_CSV_HEADER_DROP_FILTER = 'csv_header_drop_filter'
|
||||
# CSV Rows GAM should filter
|
||||
GC_CSV_ROW_FILTER = 'csv_row_filter'
|
||||
# CSV Rows GAM should filter/drop
|
||||
GC_CSV_ROW_DROP_FILTER = 'csv_row_drop_filter'
|
||||
# Minimum TLS Version required for HTTPS connections
|
||||
GC_TLS_MIN_VERSION = 'tls_min_ver'
|
||||
# Maximum TLS Version used for HTTPS connections
|
||||
@@ -1258,6 +1298,7 @@ GC_Defaults = {
|
||||
GC_CSV_HEADER_FILTER: '',
|
||||
GC_CSV_HEADER_DROP_FILTER: '',
|
||||
GC_CSV_ROW_FILTER: '',
|
||||
GC_CSV_ROW_DROP_FILTER: '',
|
||||
GC_TLS_MIN_VERSION: TLS_MIN,
|
||||
GC_TLS_MAX_VERSION: None,
|
||||
GC_CA_FILE: None,
|
||||
@@ -1372,6 +1413,9 @@ GC_VAR_INFO = {
|
||||
GC_CSV_ROW_FILTER: {
|
||||
GC_VAR_TYPE: GC_TYPE_ROWFILTER
|
||||
},
|
||||
GC_CSV_ROW_DROP_FILTER: {
|
||||
GC_VAR_TYPE: GC_TYPE_ROWFILTER
|
||||
},
|
||||
GC_TLS_MIN_VERSION: {
|
||||
GC_VAR_TYPE: GC_TYPE_STRING
|
||||
},
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
admin.googleapis.com
|
||||
alertcenter.googleapis.com
|
||||
appsactivity.googleapis.com
|
||||
calendar-json.googleapis.com
|
||||
chat.googleapis.com
|
||||
chromepolicy.googleapis.com
|
||||
classroom.googleapis.com
|
||||
cloudidentity.googleapis.com
|
||||
contacts.googleapis.com
|
||||
drive.googleapis.com
|
||||
driveactivity.googleapis.com
|
||||
iap.googleapis.com
|
||||
gmail.googleapis.com
|
||||
groupssettings.googleapis.com
|
||||
|
||||
@@ -6,5 +6,6 @@ google-auth-httplib2
|
||||
google-auth-oauthlib>=0.4.1
|
||||
google-auth>=1.11.2
|
||||
httplib2>=0.17.0
|
||||
passlib>=1.7.2; sys_platform == 'win32'
|
||||
passlib>=1.7.2
|
||||
python-dateutil
|
||||
yubikey-manager>=4.0.0
|
||||
|
||||
Binary file not shown.
@@ -1,38 +0,0 @@
|
||||
cd src
|
||||
if [[ "$TRAVIS_JOB_NAME" == *"Testing" ]]; then
|
||||
export gam="$python -m gam"
|
||||
export gampath=$(readlink -e .)
|
||||
else
|
||||
export gampath="dist/gam"
|
||||
rm -rf $gampath
|
||||
mkdir -p $gampath
|
||||
export gampath=$(readlink -e $gampath)
|
||||
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath $gampath gam.spec
|
||||
export gam="${gampath}/gam"
|
||||
export GAMVERSION=`$gam version simple`
|
||||
cp LICENSE $gampath
|
||||
cp GamCommands.txt $gampath
|
||||
this_glibc_ver=$(ldd --version | awk '/ldd/{print $NF}')
|
||||
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM-glibc$this_glibc_ver.tar.xz
|
||||
rm $gampath/lastupdatecheck.txt
|
||||
# tar will cd to dist and tar up gam/
|
||||
tar -C dist/ --create --file $GAM_ARCHIVE --xz gam
|
||||
echo "PyInstaller GAM info:"
|
||||
du -h $gam
|
||||
time $gam version extended
|
||||
if [ "${TRAVIS_DIST}" == "xenial" ] && [ "${PLATFORM}" == "x86_64" ]; then
|
||||
GAM_LEGACY_ARCHIVE=gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-legacy.tar.xz
|
||||
$python -OO -m staticx -l /lib/x86_64-linux-gnu/libresolv.so.2 -l /lib/x86_64-linux-gnu/libnss_dns.so.2 $gam $gam-staticx
|
||||
strip $gam-staticx
|
||||
rm $gampath/gam
|
||||
mv $gam-staticx $gam
|
||||
chmod 755 $gam
|
||||
rm $gampath/lastupdatecheck.txt
|
||||
tar -C dist/ --create --file $GAM_LEGACY_ARCHIVE --xz gam
|
||||
echo "Legacy StaticX GAM info:"
|
||||
du -h $gam
|
||||
time $gam version extended
|
||||
fi
|
||||
echo "GAM packages:"
|
||||
ls -l gam-*.tar.xz
|
||||
fi
|
||||
@@ -1,111 +0,0 @@
|
||||
mypath=$HOME
|
||||
whereibelong=$(pwd)
|
||||
cpucount=$(sysctl -n hw.ncpu)
|
||||
echo "This device has $cpucount CPUs for compiling..."
|
||||
|
||||
#echo "Brew installing xz..."
|
||||
#brew install xz > /dev/null
|
||||
|
||||
#brew upgrade
|
||||
|
||||
# prefer standard GNU tools like date over MacOS defaults
|
||||
export PATH="/usr/local/opt/coreutils/libexec/gnubin:$(brew --prefix)/opt/gnu-tar/libexec/gnubin:$PATH"
|
||||
|
||||
cd ~
|
||||
|
||||
#if [ ! -f python-$MIN_PYTHON_VERSION-macosx10.9.pkg ]; then
|
||||
# wget --quiet https://www.python.org/ftp/python/$MIN_PYTHON_VERSION/python-$MIN_PYTHON_VERSION-macosx10.9.pkg
|
||||
#fi
|
||||
#sudo installer -pkg python-$MIN_PYTHON_VERSION-macosx10.9.pkg -target /
|
||||
|
||||
#brew install openssl@1.1
|
||||
#brew upgrade python
|
||||
|
||||
#export python=python3
|
||||
#export pip=pip3
|
||||
|
||||
#echo "Python location:"
|
||||
#which $python
|
||||
|
||||
cd ~
|
||||
|
||||
export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
|
||||
export openssl=~/ssl/bin/openssl
|
||||
export python=~/python/bin/python3
|
||||
export pip=~/python/bin/pip3
|
||||
SSLVER=$($openssl version)
|
||||
SSLRESULT=$?
|
||||
PYVER=$($python -V)
|
||||
PYRESULT=$?
|
||||
if [ $SSLRESULT -ne 0 ] || [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]] || [ $PYRESULT -ne 0 ] || [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION"* ]]; then
|
||||
echo "SSL Result: $SSLRESULT - SSL Ver: $SSLVER - Py Result: $PYRESULT - Py Ver: $PYVER"
|
||||
if [ $SSLRESULT -ne 0 ]; then
|
||||
echo "sslresult -ne 0"
|
||||
fi
|
||||
if [[ "$SSLVER" != "OpenSSL $BUILD_OPENSSL_VERSION "* ]]; then
|
||||
echo "sslver not equal to..."
|
||||
fi
|
||||
if [ $PYRESULT -ne 0 ]; then
|
||||
echo "pyresult -ne 0"
|
||||
fi
|
||||
if [[ "$PYVER" != "Python $BUILD_PYTHON_VERSION" ]]; then
|
||||
echo "pyver not equal to..."
|
||||
fi
|
||||
|
||||
# Start clean
|
||||
rm -rf python
|
||||
rm -rf ssl
|
||||
mkdir python
|
||||
mkdir ssl
|
||||
|
||||
# Compile latest OpenSSL
|
||||
wget --quiet https://www.openssl.org/source/openssl-$BUILD_OPENSSL_VERSION.tar.gz
|
||||
echo "Extracting OpenSSL..."
|
||||
tar xf openssl-$BUILD_OPENSSL_VERSION.tar.gz
|
||||
cd openssl-$BUILD_OPENSSL_VERSION
|
||||
echo "Compiling OpenSSL $BUILD_OPENSSL_VERSION..."
|
||||
./config shared --prefix=$HOME/ssl
|
||||
echo "Running make for OpenSSL..."
|
||||
make -j$cpucount -s
|
||||
echo "Running make install for OpenSSL..."
|
||||
make install > /dev/null
|
||||
cd ~
|
||||
|
||||
# Compile latest Python
|
||||
echo "Downloading Python $BUILD_PYTHON_VERSION..."
|
||||
curl -O https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
echo "Extracting Python..."
|
||||
tar xf Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
cd Python-$BUILD_PYTHON_VERSION
|
||||
echo "Compiling Python $BUILD_PYTHON_VERSION..."
|
||||
safe_flags="--with-openssl=$HOME/ssl --enable-shared --prefix=$HOME/python --with-ensurepip=upgrade"
|
||||
unsafe_flags="--enable-optimizations --with-lto"
|
||||
if [ ! -e Makefile ]; then
|
||||
echo "running configure with safe and unsafe"
|
||||
./configure $safe_flags $unsafe_flags > /dev/null
|
||||
fi
|
||||
make -j$cpucount PROFILE_TASK="-m test.regrtest --pgo -j$(( $cpucount * 2 ))" -s
|
||||
RESULT=$?
|
||||
echo "First make exited with $RESULT"
|
||||
if [ $RESULT != 0 ]; then
|
||||
echo "Trying Python compile again without unsafe flags..."
|
||||
make clean
|
||||
./configure $safe_flags > /dev/null
|
||||
make -j$cpucount -s
|
||||
echo "Sticking with safe Python for now..."
|
||||
fi
|
||||
echo "Installing Python..."
|
||||
make install > /dev/null
|
||||
cd ~
|
||||
fi
|
||||
|
||||
$python -V
|
||||
|
||||
cd $whereibelong
|
||||
|
||||
#export PATH=/usr/local/opt/python/libexec/bin:$PATH
|
||||
$pip install --upgrade pip
|
||||
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
|
||||
$pip install --upgrade -r src/requirements.txt
|
||||
$pip install --upgrade git+git://github.com/pyinstaller/pyinstaller.git@$PYINSTALLER_COMMIT
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
if [[ "$PLATFORM" == "x86_64" ]]; then
|
||||
export BITS="64"
|
||||
export PYTHONFILE_BITS="-amd64"
|
||||
export OPENSSL_BITS="-x64"
|
||||
export WIX_BITS="x64"
|
||||
elif [[ "$PLATFORM" == "x86" ]]; then
|
||||
export BITS="32"
|
||||
export PYTHONFILE_BITS=""
|
||||
export OPENSSL_BITS=""
|
||||
export WIX_BITS="x86"
|
||||
fi
|
||||
echo "This is a ${BITS}-bit build for ${PLATFORM}"
|
||||
|
||||
export mypath=$(pwd)
|
||||
cd ~
|
||||
|
||||
# .NET Core
|
||||
echo "Installing Net-Framework-Core..."
|
||||
until powershell Install-WindowsFeature Net-Framework-Core; do echo "trying .net again..."; done
|
||||
|
||||
# VS 2015
|
||||
echo "Installing Visual Studio 2015.."
|
||||
until choco install vcbuildtools; do echo "Trying Visual Studio again..."; done
|
||||
|
||||
# Python
|
||||
echo "Installing Python..."
|
||||
export python_file=python-${BUILD_PYTHON_VERSION}${PYTHONFILE_BITS}.exe
|
||||
if [ ! -e $python_file ]; then
|
||||
echo "Downloading $python_file..."
|
||||
wget --quiet https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/$python_file
|
||||
fi
|
||||
until powershell ".\\${python_file} /quiet InstallAllUsers=1 TargetDir=c:\\python"; do echo "trying python again..."; done
|
||||
export python=/c/python/python.exe
|
||||
export pip=/c/python/scripts/pip.exe
|
||||
until [ -f $python ]; do sleep 1; done
|
||||
export PATH=$PATH:/c/python/scripts
|
||||
|
||||
# OpenSSL
|
||||
echo "Installing OpenSSL..."
|
||||
export exefile=Win${BITS}OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
|
||||
if [ ! -e $exefile ]; then
|
||||
echo "Downloading $exefile..."
|
||||
wget --quiet https://slproweb.com/download/$exefile
|
||||
fi
|
||||
until powershell ".\\${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl"; do echo "trying openssl again..."; done
|
||||
until cp -v /c/ssl/libcrypto-1_1${OPENSSL_BITS}.dll /c/python/DLLs/; do echo "trying libcrypto copy again..."; sleep 3; done
|
||||
until cp -v /c/ssl/libssl-1_1${OPENSSL_BITS}.dll /c/python/DLLs/; do echo "trying libssl copy again..."; done
|
||||
if [[ "$PLATFORM" == "x86_64" ]]; then
|
||||
cp -v /c/python/DLLs/libssl-1_1-x64.dll /c/python/DLLs/libssl-1_1.dll
|
||||
cp -v /c/python/DLLs/libcrypto-1_1-x64.dll /c/python/DLLs/libcrypto-1_1.dll
|
||||
fi
|
||||
|
||||
# WIX Toolset
|
||||
until cinst -y wixtoolset; do echo "trying wix install again..."; done
|
||||
|
||||
cd $mypath
|
||||
|
||||
$pip install --upgrade pip
|
||||
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
|
||||
$pip install --upgrade -r src/requirements.txt
|
||||
#$pip install --upgrade pyinstaller
|
||||
# Install PyInstaller from source and build bootloader
|
||||
# to try and avoid getting flagged as malware since
|
||||
# lots of malware uses PyInstaller default bootloader
|
||||
# https://stackoverflow.com/questions/53584395/how-to-recompile-the-bootloader-of-pyinstaller
|
||||
echo "Downloading PyInstaller..."
|
||||
wget --quiet https://github.com/pyinstaller/pyinstaller/archive/$PYINSTALLER_COMMIT.tar.gz
|
||||
tar xf $PYINSTALLER_COMMIT.tar.gz
|
||||
mv pyinstaller-$PYINSTALLER_COMMIT pyinstaller
|
||||
cd pyinstaller/bootloader
|
||||
echo "bootloader before:"
|
||||
md5sum ../PyInstaller/bootloader/Windows-${BITS}bit/*
|
||||
|
||||
$python ./waf all --target-arch=${BITS}bit --msvc_version "msvc 14.0"
|
||||
|
||||
echo "bootloader after:"
|
||||
md5sum ../PyInstaller/bootloader/Windows-${BITS}bit/*
|
||||
echo "PATH: $PATH"
|
||||
cd ..
|
||||
$python setup.py install
|
||||
echo "cd to $mypath"
|
||||
cd $mypath
|
||||
Reference in New Issue
Block a user