mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-05 14:51:39 +00:00
Compare commits
137 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9af0a5d843 | ||
|
|
3313295532 | ||
|
|
fdf6c147dc | ||
|
|
323dbd5ca9 | ||
|
|
d01fd74fa3 | ||
|
|
8c33b88e3e | ||
|
|
5d11397fca | ||
|
|
995321978f | ||
|
|
448789dad0 | ||
|
|
e9ba6819ba | ||
|
|
3056c7b803 | ||
|
|
f2c28fd1f7 | ||
|
|
11e4ff1eb5 | ||
|
|
81cd74c244 | ||
|
|
faade7c057 | ||
|
|
0032066e1d | ||
|
|
dd938baced | ||
|
|
b835b6ee36 | ||
|
|
3660d65df6 | ||
|
|
3e0b4125e0 | ||
|
|
9820a3d81e | ||
|
|
b670a4cee6 | ||
|
|
a5dd5275c8 | ||
|
|
9b6ad2fa60 | ||
|
|
1d80028c93 | ||
|
|
a013e95fcf | ||
|
|
eb4d6ece3f | ||
|
|
a50d1ef456 | ||
|
|
c179ed732c | ||
|
|
a85a313ebb | ||
|
|
534ccd275d | ||
|
|
3c3d043276 | ||
|
|
786adb7c44 | ||
|
|
bb6c8dc225 | ||
|
|
a7cd88b2be | ||
|
|
a9fad337e2 | ||
|
|
d4dc1b1589 | ||
|
|
ad94adbb53 | ||
|
|
b692799dcb | ||
|
|
04dcf47746 | ||
|
|
aebb3c44fe | ||
|
|
8cf345196a | ||
|
|
173fdb2297 | ||
|
|
120db6e7d8 | ||
|
|
55555506be | ||
|
|
41965e962d | ||
|
|
30fdd00d65 | ||
|
|
37e3fd904d | ||
|
|
dc22b024b8 | ||
|
|
f412d5ad4c | ||
|
|
24cfe807e6 | ||
|
|
6a721ac2c1 | ||
|
|
4a4b22dfba | ||
|
|
6d4524c153 | ||
|
|
d7b2f82a4a | ||
|
|
844a2fe1e8 | ||
|
|
baf822c685 | ||
|
|
f3169a631c | ||
|
|
d171db36bc | ||
|
|
34c7576cd5 | ||
|
|
f859d0678b | ||
|
|
0986cb3fd9 | ||
|
|
645fd9a135 | ||
|
|
9582e6840a | ||
|
|
a8a9cfb2ab | ||
|
|
5519b33a08 | ||
|
|
976ef0252e | ||
|
|
e6829d0804 | ||
|
|
9f985a7b26 | ||
|
|
a628aeb1a8 | ||
|
|
d81c80b150 | ||
|
|
63ee016691 | ||
|
|
4935385572 | ||
|
|
30069d3039 | ||
|
|
3ef8a5a762 | ||
|
|
b12fda5007 | ||
|
|
26925c30c1 | ||
|
|
4085816fa3 | ||
|
|
7e36e5abe6 | ||
|
|
2037189148 | ||
|
|
c7781e66e1 | ||
|
|
8843675ad4 | ||
|
|
c05a1ea6b4 | ||
|
|
d9a5ac849b | ||
|
|
51d4c29dd5 | ||
|
|
c2bb9cbdaf | ||
|
|
d185765831 | ||
|
|
f57f311f16 | ||
|
|
4c81849c60 | ||
|
|
156c8319d9 | ||
|
|
b8de3310d0 | ||
|
|
f28cf664cb | ||
|
|
02b876155a | ||
|
|
97bd1f71c3 | ||
|
|
8be4445f0d | ||
|
|
550cf47db4 | ||
|
|
05d32eec08 | ||
|
|
59c181eeda | ||
|
|
dd5fd2a2c3 | ||
|
|
6ab8fbf538 | ||
|
|
509919da84 | ||
|
|
04bd5f36a0 | ||
|
|
801f5b7861 | ||
|
|
09d86e1220 | ||
|
|
6110aa1d32 | ||
|
|
11e6c80dbf | ||
|
|
1f32536ff7 | ||
|
|
7979206f21 | ||
|
|
f7901790ad | ||
|
|
7fae16f962 | ||
|
|
1dd76012f8 | ||
|
|
8fd3f4ee7d | ||
|
|
e30b8ed53e | ||
|
|
e0960d9113 | ||
|
|
35dda1cd34 | ||
|
|
ef2253fe58 | ||
|
|
ecea3aed7e | ||
|
|
2e81cae271 | ||
|
|
080eede356 | ||
|
|
fe37c687e4 | ||
|
|
27efef1d9b | ||
|
|
52aa1ac0da | ||
|
|
b5c23fdb83 | ||
|
|
0b16c9aef4 | ||
|
|
3be97acd9c | ||
|
|
8df8e6797f | ||
|
|
156ba44656 | ||
|
|
1b3663d60c | ||
|
|
8f0ea2f6a5 | ||
|
|
5e34b12e5c | ||
|
|
d124575a91 | ||
|
|
f5364ab4d0 | ||
|
|
b5580c5649 | ||
|
|
e9200ea8fb | ||
|
|
2e0c280ea6 | ||
|
|
3948a414b5 | ||
|
|
2c83068605 |
BIN
.github/actions/creds.tar.gpg
vendored
BIN
.github/actions/creds.tar.gpg
vendored
Binary file not shown.
BIN
.github/actions/creds.tar.xz.gpg
vendored
Normal file
BIN
.github/actions/creds.tar.xz.gpg
vendored
Normal file
Binary file not shown.
3
.github/actions/decrypt.sh
vendored
3
.github/actions/decrypt.sh
vendored
@@ -14,4 +14,5 @@ gpg --quiet --batch --yes --decrypt --passphrase="${PASSCODE}" \
|
||||
--output "${credsfile}" "${gpgfile}"
|
||||
|
||||
tar xvvf "${credsfile}" --directory "${gampath}"
|
||||
ls -l "${gampath}"
|
||||
rm -rvf "${gpgfile}"
|
||||
rm -rvf "${credsfile}"
|
||||
|
||||
221
.github/workflows/build.yml
vendored
221
.github/workflows/build.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
goal: build
|
||||
arch: x86_64
|
||||
openssl_archs: linux-x86_64
|
||||
- os: [self-hosted, linux, arm64]
|
||||
- os: [self-hosted, linux, arm64, gcp]
|
||||
jid: 2
|
||||
goal: build
|
||||
arch: aarch64
|
||||
@@ -66,27 +66,36 @@ jobs:
|
||||
arch: x86_64
|
||||
- os: ubuntu-22.04
|
||||
goal: test
|
||||
python: "3.11-dev"
|
||||
python: "3.10"
|
||||
jid: 10
|
||||
arch: x86_64
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache multiple paths
|
||||
uses: actions/cache@v2
|
||||
if: matrix.goal == 'build'
|
||||
uses: actions/cache@v3
|
||||
id: cache-python-ssl
|
||||
with:
|
||||
path: |
|
||||
bin
|
||||
key: gam-${{ matrix.jid }}-20220802
|
||||
bin.tar.xz
|
||||
src/cpython
|
||||
key: gam-${{ matrix.jid }}-20221101
|
||||
|
||||
- name: Untar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
tar xvvf bin.tar.xz
|
||||
|
||||
- name: Use pre-compiled Python for testing
|
||||
if: matrix.python != ''
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
|
||||
@@ -117,11 +126,11 @@ jobs:
|
||||
sudo apt-get -qq --yes update
|
||||
sudo apt-get -qq --yes install swig libpcsclite-dev
|
||||
|
||||
- name: MacOS remove Homebrew
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
# remove everything except the libraries needed by yubikey-manager
|
||||
brew uninstall $(brew list | grep -v 'pcre\|swig\|pcsc-lite')
|
||||
#- name: MacOS remove Homebrew
|
||||
# if: runner.os == 'macOS'
|
||||
# run: |
|
||||
# # remove everything except the libraries needed by yubikey-manager
|
||||
# brew uninstall $(brew list | grep -v 'pcre\|swig\|pcsc-lite')
|
||||
|
||||
- name: MacOS install tools
|
||||
if: runner.os == 'macOS'
|
||||
@@ -147,6 +156,7 @@ jobs:
|
||||
openssl_archs: ${{ matrix.openssl_archs }}
|
||||
run: |
|
||||
echo "We are running on ${RUNNER_OS}"
|
||||
LD_LIBRARY_PATH="${OPENSSL_INSTALL_PATH}/lib:${PYTHON_INSTALL_PATH}/lib"
|
||||
if [[ "${arch}" == "Win64" ]]; then
|
||||
PYEXTERNALS_PATH="amd64"
|
||||
PYBUILDRELEASE_ARCH="x64"
|
||||
@@ -178,21 +188,21 @@ jobs:
|
||||
MAKE=nmake
|
||||
MAKEOPT=""
|
||||
PERL="c:\strawberry\perl\bin\perl.exe"
|
||||
echo "PYTHON=${PYTHON_INSTALL_PATH}\python.exe" >> $GITHUB_ENV
|
||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${PYTHON_SOURCE_PATH}/PCbuild/${PYEXTERNALS_PATH}"
|
||||
echo "PYTHON=${PYTHON_SOURCE_PATH}/PCbuild/${PYEXTERNALS_PATH}/python.exe" >> $GITHUB_ENV
|
||||
echo "GAM_ARCHIVE_ARCH=${GAM_ARCHIVE_ARCH}" >> $GITHUB_ENV
|
||||
echo "WIX_ARCH=${WIX_ARCH}" >> $GITHUB_ENV
|
||||
fi
|
||||
echo "We'll run make with: ${MAKEOPT}"
|
||||
echo "JID=${jid}" >> $GITHUB_ENV
|
||||
echo "arch=${arch}" >> $GITHUB_ENV
|
||||
echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}" >> $GITHUB_ENV
|
||||
echo "MAKE=${MAKE}" >> $GITHUB_ENV
|
||||
echo "MAKEOPT=${MAKEOPT}" >> $GITHUB_ENV
|
||||
echo "PERL=${PERL}" >> $GITHUB_ENV
|
||||
echo "PYEXTERNALS_PATH=${PYEXTERNALS_PATH}" >> $GITHUB_ENV
|
||||
echo "PYBUILDRELEASE_ARCH=${PYBUILDRELEASE_ARCH}" >> $GITHUB_ENV
|
||||
echo "openssl_archs=${openssl_archs}" >> $GITHUB_ENV
|
||||
echo "LD_LIBRARY_PATH=${OPENSSL_INSTALL_PATH}/lib:${PYTHON_INSTALL_PATH}/lib" >> $GITHUB_ENV
|
||||
#echo "PATH=${PATH}:${PYTHON_INSTALL_PATH}/scripts" >> $GITHUB_ENV
|
||||
|
||||
- name: Get latest stable OpenSSL source
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||
@@ -330,7 +340,7 @@ jobs:
|
||||
$env:OPENSSL_EXT_TARGET_PATH = "${env:OPENSSL_EXT_PATH}${env:PYEXTERNALS_PATH}"
|
||||
echo "Copying our OpenSSL to ${env:OPENSSL_EXT_TARGET_PATH}"
|
||||
mkdir "${env:OPENSSL_EXT_TARGET_PATH}\include\openssl\"
|
||||
Copy-Item -Path "${env:GITHUB_WORKSPACE}/src/openssl-${env:openssl_archs}\LICENSE.txt" -Destination "${env:OPENSSL_EXT_TARGET_PATH}\LICENSE"
|
||||
Copy-Item -Path "${env:GITHUB_WORKSPACE}/src/openssl-${env:openssl_archs}\LICENSE.txt" -Destination "${env:OPENSSL_EXT_TARGET_PATH}\LICENSE" -Verbose
|
||||
cp -v "$env:OPENSSL_INSTALL_PATH\lib\*" "${env:OPENSSL_EXT_TARGET_PATH}"
|
||||
cp -v "$env:OPENSSL_INSTALL_PATH\bin\*" "${env:OPENSSL_EXT_TARGET_PATH}"
|
||||
cp -v "$env:OPENSSL_INSTALL_PATH\include\openssl\*" "${env:OPENSSL_EXT_TARGET_PATH}\include\openssl\"
|
||||
@@ -350,22 +360,10 @@ jobs:
|
||||
run: |
|
||||
cd "${env:PYTHON_SOURCE_PATH}"
|
||||
# We need out custom openssl.props which uses OpenSSL 3 DLL names
|
||||
Copy-Item -Path "${env:GITHUB_WORKSPACE}\src\tools\openssl.props" -Destination PCBuild\
|
||||
Copy-Item -Path "${env:GITHUB_WORKSPACE}\src\tools\openssl.props" -Destination PCBuild\ -Verbose
|
||||
echo "Building for ${env:PYBUILDRELEASE_ARCH}..."
|
||||
PCBuild\build.bat -m --pgo -c Release -p "${env:PYBUILDRELEASE_ARCH}"
|
||||
|
||||
- name: Windows Install Python
|
||||
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||
shell: powershell
|
||||
run: |
|
||||
cd "${env:PYTHON_SOURCE_PATH}"
|
||||
mkdir "${env:PYTHON_INSTALL_PATH}\lib"
|
||||
mkdir "${env:PYTHON_INSTALL_PATH}\include"
|
||||
Copy-Item -Path "PCBuild\${env:PYEXTERNALS_PATH}\*" "${env:PYTHON_INSTALL_PATH}\"
|
||||
Copy-Item -Path "${env:PYTHON_SOURCE_PATH}\Lib\*" "${env:PYTHON_INSTALL_PATH}\lib\" -recurse
|
||||
Copy-Item -Path "${env:PYTHON_SOURCE_PATH}\Include\*" "${env:PYTHON_INSTALL_PATH}\include\" -recurse
|
||||
Copy-Item -Path "${env:PYTHON_SOURCE_PATH}\PC\*.h" "${env:PYTHON_INSTALL_PATH}\include\"
|
||||
|
||||
- name: Mac/Linux Build Python
|
||||
if: matrix.goal == 'build' && runner.os != 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
@@ -379,6 +377,9 @@ jobs:
|
||||
cd "${PYTHON_SOURCE_PATH}"
|
||||
$MAKE altinstall
|
||||
$MAKE bininstall
|
||||
export PATH="${PATH}:${PYTHON_INSTALL_PATH}/bin"
|
||||
echo "PATH=${PATH}" >> $GITHUB_ENV
|
||||
echo "PATH: ${PATH}"
|
||||
|
||||
- name: Run Python
|
||||
run: |
|
||||
@@ -391,7 +392,22 @@ jobs:
|
||||
"${PYTHON}" -m pip install --upgrade pip
|
||||
"${PYTHON}" -m pip install --upgrade wheel
|
||||
"${PYTHON}" -m pip install --upgrade setuptools
|
||||
|
||||
|
||||
- name: Install pip requirements
|
||||
run: |
|
||||
if [[ "${RUNNER_OS}" == "macOS" ]]; then
|
||||
"${PYTHON}" -m pip install --upgrade cffi ${PIP_ARGS}
|
||||
"${PYTHON}" -m pip download --only-binary :all: \
|
||||
--dest . \
|
||||
--no-cache \
|
||||
--no-deps \
|
||||
--platform macosx_10_15_universal2 \
|
||||
cryptography
|
||||
"${PYTHON}" -m pip install --force-reinstall --no-deps cryptography*.whl
|
||||
fi
|
||||
"${PYTHON}" -m pip install --upgrade -r requirements.txt ${PIP_ARGS}
|
||||
"${PYTHON}" -m pip list
|
||||
|
||||
- name: Install PyInstaller
|
||||
if: matrix.goal == 'build'
|
||||
run: |
|
||||
@@ -407,27 +423,9 @@ jobs:
|
||||
fi
|
||||
echo "PyInstaller build arguments: ${PYINSTALLER_BUILD_ARGS}"
|
||||
"${PYTHON}" ./waf all $PYINSTALLER_BUILD_ARGS
|
||||
cd ../..
|
||||
cd ..
|
||||
echo "---- Installing PyInstaller ----"
|
||||
"${PYTHON}" -m pip install pyinstaller
|
||||
|
||||
- name: Install pip requirements
|
||||
run: |
|
||||
if [[ "${RUNNER_OS}" == "macOS" ]]; then
|
||||
for package in cryptography; do
|
||||
"${PYTHON}" -m pip install --upgrade cffi ${PIP_ARGS}
|
||||
"${PYTHON}" -m pip download --only-binary :all: \
|
||||
--dest . \
|
||||
--no-cache \
|
||||
--no-deps \
|
||||
--platform macosx_10_15_universal2 \
|
||||
$package
|
||||
"${PYTHON}" -m pip install --force-reinstall --no-deps $package*.whl
|
||||
done
|
||||
find $PYTHON_INSTALL_PATH/lib/python3.10/site-packages -type f -name "*.so" -exec du -sh "{}" \;
|
||||
fi
|
||||
"${PYTHON}" -m pip install --upgrade -r requirements.txt ${PIP_ARGS}
|
||||
"${PYTHON}" -m pip list
|
||||
"${PYTHON}" -m pip install .
|
||||
|
||||
- name: Build GAM with PyInstaller
|
||||
if: matrix.goal != 'test'
|
||||
@@ -436,6 +434,10 @@ jobs:
|
||||
mkdir -p -v "${gampath}"
|
||||
if [[ "${RUNNER_OS}" == "macOS" ]]; then
|
||||
export gampath=$($PYTHON -c "import os; print(os.path.realpath('$gampath'))")
|
||||
elif [[ "${RUNNER_OS}" == "Windows" ]]; then
|
||||
# Work around issue where PyInstaller picks up python3.dll from other Python versions
|
||||
# https://github.com/pyinstaller/pyinstaller/issues/7102
|
||||
export PATH="/usr/bin"
|
||||
else
|
||||
export gampath=$(realpath "${gampath}")
|
||||
fi
|
||||
@@ -445,6 +447,16 @@ jobs:
|
||||
echo -e "GAM: ${gam}\nGAMPATH: ${gampath}"
|
||||
"${PYTHON}" -m PyInstaller --clean --distpath="${gampath}" gam.spec
|
||||
|
||||
- name: Copy extra package files
|
||||
if: matrix.goal == 'build'
|
||||
run: |
|
||||
cp -v roots.pem $gampath
|
||||
cp -v LICENSE $gampath
|
||||
cp -v GamCommands.txt $gampath
|
||||
if [[ "${RUNNER_OS}" == "Windows" ]]; then
|
||||
cp -v gam-setup.bat $gampath
|
||||
fi
|
||||
|
||||
- name: Basic Tests all jobs
|
||||
run: |
|
||||
$PYTHON -m unittest discover --start-directory ./ --pattern "*_test.py" --buffer
|
||||
@@ -456,8 +468,6 @@ jobs:
|
||||
- name: Linux/MacOS package
|
||||
if: runner.os != 'Windows' && matrix.goal == 'build'
|
||||
run: |
|
||||
cp -v LICENSE $gampath
|
||||
cp -v GamCommands.txt $gampath
|
||||
if [[ "${RUNNER_OS}" == "macOS" ]]; then
|
||||
GAM_ARCHIVE="gam-${GAMVERSION}-macos-universal2.tar.xz"
|
||||
elif [[ "${RUNNER_OS}" == "Linux" ]]; then
|
||||
@@ -501,9 +511,6 @@ jobs:
|
||||
- name: Windows package
|
||||
if: runner.os == 'Windows' && matrix.goal != 'test'
|
||||
run: |
|
||||
cp -v LICENSE $gampath
|
||||
cp -v GamCommands.txt $gampath
|
||||
cp -v gam-setup.bat $gampath
|
||||
cd dist/
|
||||
GAM_ARCHIVE="../gam-${GAMVERSION}-windows-${GAM_ARCHIVE_ARCH}.zip"
|
||||
/c/Program\ Files/7-Zip/7z.exe a -tzip $GAM_ARCHIVE gam "-xr@${GITHUB_WORKSPACE}/.github/actions/package_exclusions.txt" -bb3
|
||||
@@ -540,7 +547,7 @@ jobs:
|
||||
if [[ "${RUNNER_OS}" == "macOS" ]]; then
|
||||
brew install gnupg
|
||||
fi
|
||||
source ../.github/actions/decrypt.sh ../.github/actions/creds.tar.gpg creds.tar
|
||||
source ../.github/actions/decrypt.sh ../.github/actions/creds.tar.xz.gpg creds.tar.xz
|
||||
export OAUTHFILE="oauth2.txt-gam-gha-${JID}"
|
||||
echo "OAUTHFILE=${OAUTHFILE}" >> $GITHUB_ENV
|
||||
export gam_user="gam-gha-${JID}@pdl.jaylee.us"
|
||||
@@ -550,20 +557,33 @@ jobs:
|
||||
$gam info domain
|
||||
$gam oauth refresh
|
||||
$gam info user
|
||||
#$gam info user $gam_user grouptree
|
||||
export tstamp=$($PYTHON -c "import time; print(time.time_ns())")
|
||||
export newbase=gha_test_$JID_$tstamp
|
||||
export newuser=$newbase@pdl.jaylee.us
|
||||
export newgroup=$newbase-group@pdl.jaylee.us
|
||||
export newalias=$newbase-alias@pdl.jaylee.us
|
||||
export newbuilding=$newbase-building
|
||||
export newresource=$newbase-resource
|
||||
export newbase="gha_test_${JID}_${tstamp}"
|
||||
export newuser="${newbase}@pdl.jaylee.us"
|
||||
export newgroup="${newbase}-group@pdl.jaylee.us"
|
||||
export newalias="${newbase}-alias@pdl.jaylee.us"
|
||||
export newbuilding="${newbase}-building"
|
||||
export newresource="${newbase}-resource"
|
||||
export newou="aaaGithub Actions/${newbase}"
|
||||
|
||||
# cleanup old runs
|
||||
GAM_CSV_ROW_FILTER="name:regex:gha_test_${JID}_" $gam print vaultholds | $gam csv - gam delete vaulthold "id:~~holdId~~" matter "id:~~matterId~~"
|
||||
GAM_CSV_ROW_FILTER="name:regex:gha_test_${JID}_" $gam print features | $gam csv - gam delete feature ~name
|
||||
GAM_CSV_ROW_FILTER="name:regex:^gha_test_${JID}_" $gam user $gam_user print shareddrives asadmin | $gam csv - gam user $gam_user delete shareddrive ~id nukefromorbit
|
||||
$gam print users query "gha.jid=$JID" | $gam csv - gam delete user ~primaryEmail
|
||||
GAM_CSV_ROW_FILTER="name:regex:^gha_test_${JID}_" $gam print ous fromparent "aaaGithub Actions" | $gam csv - gam delete ou ~orgUnitId
|
||||
GAM_CSV_ROW_FILTER="groupKey.id:regex:^gha_test_${JID}_" $gam print cigroups | $gam csv - gam delete cigroup ~groupKey.id
|
||||
GAM_CSV_ROW_FILTER="resourceId:regex:^gha_test_${JID}_" $gam print resources | $gam csv - gam delete resource ~resourceId
|
||||
GAM_CSV_ROW_FILTER="buildingId:regex:^gha_test_${JID}_" $gam print buildings | $gam csv - gam delete building ~buildingId
|
||||
|
||||
echo "Creating OrgUnit ${newou}"
|
||||
$gam create ou "${newou}"
|
||||
export GAM_THREADS=5
|
||||
echo email > sample.csv;
|
||||
for i in {1..10}; do
|
||||
echo "${newbase}-bulkuser-$i" >> sample.csv;
|
||||
done
|
||||
$gam create user $newuser firstname GHA lastname $JID password random recoveryphone 12125121110 recoveryemail jay0lee@gmail.com gha.jid $JID languages en+,en-GB-
|
||||
$gam create user $newuser firstname GHA lastname $JID displayname "Github Actions ${JID}" password random ou "${newou}" recoveryphone 12125121110 recoveryemail jay0lee@gmail.com gha.jid $JID languages en+,en-GB-
|
||||
$gam user $newuser update photo https://dummyimage.com/400x600/000/fff
|
||||
$gam user $newuser get photo
|
||||
$gam user $newuser delete photo
|
||||
@@ -577,8 +597,8 @@ jobs:
|
||||
$gam update group $newgroup add owner $gam_user
|
||||
$gam update group $newgroup add member $newuser
|
||||
$gam create admin $newuser _GROUPS_EDITOR_ROLE CUSTOMER # condition nonsecuritygroup
|
||||
$gam csv sample.csv gam create user ~~email~~ firstname "GHA Bulk" lastname ~~email~~ gha.jid $JID
|
||||
$gam csv sample.csv gam update user ~~email~~ recoveryphone 12125121110 recoveryemail jay0lee@gmail.com password random
|
||||
$gam csv sample.csv gam create user ~~email~~ firstname "GHA Bulk" lastname ~~email~~ gha.jid $JID ou "${newou}"
|
||||
$gam csv sample.csv gam update user ~~email~~ recoveryphone 12125121110 recoveryemail jay0lee@gmail.com password random displayname "GitHub Actions Bulk ${JID}"
|
||||
$gam csv sample.csv gam update user ~~email~~ recoveryphone "" recoveryemail ""
|
||||
$gam csv sample.csv gam user ~email add license workspaceenterpriseplus
|
||||
$gam csv sample.csv gam user $gam_user sendemail recipient ~~email~~@pdl.jaylee.us subject "test message $newbase" message "GHA test message"
|
||||
@@ -608,8 +628,8 @@ jobs:
|
||||
$gam users "$newbase-bulkuser-7 $newbase-bulkuser-8 $newbase-bulkuser-9" modify messages query in:anywhere maxtomodify 99999 addlabel IMPORTANT addlabel STARRED doit
|
||||
$gam user $newuser delete label --ALL_LABELS--
|
||||
GAM_CSV_ROW_FILTER="name:regex:gha-test-${JID}" $gam print features | $gam csv - gam delete feature ~name
|
||||
$gam create feature name Whiteboard-$newbase
|
||||
$gam create feature name VC-$newbase
|
||||
$gam create feature name Whiteboard-$newbase
|
||||
$gam create building "My Building - $newbase" id $newbuilding floors 1,2,3,4,5,6,7,8,9,10,11,12,14,15 description "No 13th floor here..."
|
||||
$gam create resource $newresource "Resource Calendar $tstamp" capacity 25 features Whiteboard-$newbase,VC-$newbase building $newbuilding floor 15 type Room
|
||||
$gam info resource $newresource
|
||||
@@ -645,8 +665,8 @@ jobs:
|
||||
$gam delete hold "GHA hold $newbase" matter $matterid
|
||||
$gam update matter $matterid action close
|
||||
$gam update matter $matterid action delete
|
||||
#$gam delete user $newuser
|
||||
#$gam undelete user $newuser
|
||||
# shakes off vault hold on user so we can delete
|
||||
$gam print users query "email:${newuser}" orgunitpath | $gam csv - gam update user ~primaryEmail ou ~orgUnitPath
|
||||
$gam delete user $newuser
|
||||
$gam print users query "gha.jid=$JID" | $gam csv - gam delete user ~primaryEmail
|
||||
$gam print mobile
|
||||
@@ -662,19 +682,24 @@ jobs:
|
||||
$gam report users fields accounts:is_less_secure_apps_access_allowed,gmail:last_imap_time,gmail:last_pop_time filters "accounts:last_login_time>2019-01-01T00:00:00.000Z" todrive
|
||||
$gam report admin start -3d todrive
|
||||
$gam print devices nopersonaldevices nodeviceusers filter "serial:$JID$JID$JID$JID-" | $gam csv - gam delete device id ~name
|
||||
#$gam print userinvitations
|
||||
#$gam print userinvitations | $gam csv - gam send userinvitation ~name
|
||||
$gam print userinvitations
|
||||
$gam print userinvitations | $gam csv - gam send userinvitation ~name
|
||||
$gam create caalevel "zzz_${newbase}" basic condition ipsubnetworks 1.1.1.1/32,2.2.2.2/32 endcondition
|
||||
$gam print caalevels
|
||||
$gam delete caalevel "zzz_${newbase}"
|
||||
driveid=$($gam user $gam_user add shareddrive "${newbase}" | awk '{print $NF}')
|
||||
echo "Created shared drive ${driveid}"
|
||||
$gam user $gam_user add drivefile localfile gam.py parentid "${driveid}"
|
||||
$gam user $gam_user update shareddrive "${driveid}" ou "id:03ph8a2z1t2ph5z"
|
||||
$gam user $gam_user update shareddrive "${driveid}" ou "${newou}"
|
||||
$gam user $gam_user show shareddrives asadmin
|
||||
$gam user $gam_user update shareddrive "${driveid}" ou "aaaGithub Actions" # so we can delete our OU...
|
||||
$gam user $gam_user delete shareddrive "${driveid}" nukefromorbit
|
||||
echo "printer model count:"
|
||||
$gam print printermodels | wc -l
|
||||
$gam create inboundssoprofile name "El Goog ${newbase}" loginurl https://www.google.com logouturl https://www.google.com changepasswordurl https://www.google.com entityid ElGoog
|
||||
$gam create inboundssocredential profile "El Goog ${newbase}" generate_key
|
||||
$gam create inboundssoassignment profile "El Goog ${newbase}" orgunit "${newou}" mode SAML_SSO
|
||||
$gam delete ou "${newou}"
|
||||
#$gam print printers
|
||||
#$gam create printer displayname "${newbase}" uri ipp://localhost:631 driverless description "made by $(gam_user)" ou /
|
||||
#export CUSTOMER_ID="C01wfv983"
|
||||
@@ -683,23 +708,49 @@ jobs:
|
||||
#echo "using delegated admin service account"
|
||||
#$gam print users
|
||||
|
||||
# - name: Upload to Google Drive, build only.
|
||||
# if: github.event_name == 'push' && matrix.goal != 'test'
|
||||
# run: |
|
||||
# ls gam-$GAMVERSION-*
|
||||
# for gamfile in gam-$GAMVERSION-*; do
|
||||
# echo "Uploading file ${gamfile} to Google Drive..."
|
||||
# fileid=$($gam user $gam_user add drivefile localfile $gamfile drivefilename $GAMVERSION-${GITHUB_SHA:0:7}-$gamfile parentid 1N2zbO33qzUQFsGM49-m9AQC1ijzd_ru1 returnidonly)
|
||||
# echo "file uploaded as ${fileid}, setting ACL..."
|
||||
# $gam user $gam_user add drivefileacl $fileid anyone role reader withlink
|
||||
# done
|
||||
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
if: github.event_name == 'push' && matrix.goal != 'test'
|
||||
uses: actions/upload-artifact@v3
|
||||
if: (github.event_name == 'push' || github.event_name == 'schedule') && matrix.goal != 'test'
|
||||
with:
|
||||
name: gam-binaries
|
||||
path: |
|
||||
src/*.tar.xz
|
||||
src/*.zip
|
||||
src/*.msi
|
||||
|
||||
- name: Tar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
tar cJvvf bin.tar.xz bin/
|
||||
|
||||
publish:
|
||||
if: github.event_name == 'push'
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
|
||||
- name: VirusTotal Scan
|
||||
uses: crazy-max/ghaction-virustotal@v3
|
||||
with:
|
||||
vt_api_key: ${{ secrets.VT_API_KEY }}
|
||||
files: |
|
||||
gam-binaries/*
|
||||
|
||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||
name: Publish draft release
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
automatic_release_tag: latest
|
||||
prerelease: false
|
||||
draft: true
|
||||
files: |
|
||||
gam-binaries/*
|
||||
|
||||
36
.github/workflows/get-roots.yml
vendored
Normal file
36
.github/workflows/get-roots.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Check for Google Root CA Updates
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '23 23 * * *'
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
working-directory: src
|
||||
|
||||
jobs:
|
||||
check-apis:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
with:
|
||||
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
|
||||
fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
|
||||
|
||||
- name: Check for updates
|
||||
run: curl -o ./roots.pem -vvvv https://pki.goog/roots.pem
|
||||
|
||||
- name: Commit file
|
||||
run: |
|
||||
git config --local user.email "action@github.com"
|
||||
git config --local user.name "GitHub Action"
|
||||
git add roots.pem
|
||||
git diff --quiet && git diff --staged --quiet || git commit -am '[ci skip] Updated roots.pem'
|
||||
|
||||
- name: Push changes
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1548,6 +1548,42 @@ gam print group-members|groups-members [todrive]
|
||||
[roles <GroupRoleList>] [membernames] [fields <MembersFieldNameList>]
|
||||
[includederivedmembership]
|
||||
|
||||
<SSOProfileDisplayName> ::= <String>
|
||||
<SSOProfileName> ::= id:inboundSamlSsoProfiles/<String>
|
||||
<SSOProfileItem> ::= <SSOProfileDisplayName>|<SSOProfileName>
|
||||
<SSOProfileItemList> ::= "<SSOProfileItem>(,<SSOProfileItem>)*"
|
||||
|
||||
gam create inboundssoprofile [name <SSOProfileDisplayName>]
|
||||
[entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
gam update inboundssoprofile <SSOProfileItem>
|
||||
[entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
gam delete inboundssoprofile <SSOProfileItem>
|
||||
gam info inboundssoprofile <SSOProfileItem>
|
||||
gam show inboundssoprofiles
|
||||
gam print inboundssoprofiles [todrive]
|
||||
|
||||
<SSOCredentialsName> ::= [id:]inboundSamlSsoProfiles/<String>/idpCredentials/<String>
|
||||
|
||||
gam create inboundssocredential profile <SSOProfileItem>
|
||||
(pemfile <FileName>)|(generatekey [keysize 1024|2048|4096]) [replaceolddest]
|
||||
gam delete inboundssocredential <SSOCredentialsName>
|
||||
gam show inboundssocredentials [profile|profiles <SSOProfileItemList>]
|
||||
gam print inboundssocredentials [profile|profiles <SSOProfileItemList>] [todrive]
|
||||
|
||||
<SSOAssignmentSelector> ::=
|
||||
groups/<String> |
|
||||
group:<EmailAddress> |
|
||||
orgunits/<String> |
|
||||
orgunit:<OrgUnitPath>
|
||||
|
||||
gam create inboundssoassignment (group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)
|
||||
(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled) [neverredirect]
|
||||
gam update inboundssoassignment [(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)]
|
||||
[(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled)] [neverredirect]
|
||||
gam info inboundssoassignment <SSOAssignmentSelector>
|
||||
gam show inboundssoassignments
|
||||
gam print inboundssoassignments [todrive]
|
||||
|
||||
gam send userinvitation <EmailAddress>
|
||||
gam cancel userinvitation <EmailAddress>
|
||||
gam check userinvitation|isinvitable <EmailAddress>
|
||||
|
||||
@@ -5,9 +5,7 @@ import sys
|
||||
import importlib
|
||||
from PyInstaller.utils.hooks import copy_metadata
|
||||
|
||||
# dynamically determine where httplib2/cacerts.txt lives
|
||||
proot = os.path.dirname(importlib.import_module('httplib2').__file__)
|
||||
extra_files = [(os.path.join(proot, 'cacerts.txt'), 'httplib2')]
|
||||
extra_files = []
|
||||
|
||||
extra_files += copy_metadata('google-api-python-client')
|
||||
extra_files += [('cbcm-v1.1beta1.json', '.')]
|
||||
|
||||
@@ -55,6 +55,9 @@
|
||||
<Component Id="gamcommands_txt" Guid="58ff9c45-a7c9-4e22-8845-a9a92610c1f3">
|
||||
<File Name="gamcommands.txt" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="roots_pem" Guid="18ff9c45-a3c9-4e22-8445-a8a92610c1f3">
|
||||
<File Name="roots.pem" KeyPath="yes" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ from gam.gapi import chromemanagement as gapi_chromemanagement
|
||||
from gam.gapi import chromepolicy as gapi_chromepolicy
|
||||
from gam.gapi.cloudidentity import devices as gapi_cloudidentity_devices
|
||||
from gam.gapi.cloudidentity import groups as gapi_cloudidentity_groups
|
||||
from gam.gapi.cloudidentity import inboundsso as gapi_cloudidentity_inboundsso
|
||||
from gam.gapi.cloudidentity import orgunits as gapi_cloudidentity_orgunits
|
||||
from gam.gapi.cloudidentity import userinvitations as gapi_cloudidentity_userinvitations
|
||||
from gam.gapi import contactdelegation as gapi_contactdelegation
|
||||
@@ -553,9 +554,12 @@ def SetGlobalVariables():
|
||||
'debug.gam',
|
||||
filePresentValue=4,
|
||||
fileAbsentValue=0)
|
||||
_getOldSignalFile(GC_LOW_MEMORY, 'lowmemory.txt')
|
||||
_getOldSignalFile(GC_NO_BROWSER, 'nobrowser.txt')
|
||||
_getOldSignalFile(GC_NO_TDEMAIL, 'notdemail.txt')
|
||||
_getOldSignalFile(GC_OAUTH_BROWSER, 'oauthbrowser.txt')
|
||||
# oauthbrowser.txt is deprecated as we now always
|
||||
# use the localhost flow.
|
||||
#_getOldSignalFile(GC_OAUTH_BROWSER, 'oauthbrowser.txt')
|
||||
# _getOldSignalFile(GC_NO_CACHE, u'nocache.txt')
|
||||
# _getOldSignalFile(GC_CACHE_DISCOVERY_ONLY, u'allcache.txt', filePresentValue=False, fileAbsentValue=True)
|
||||
_getOldSignalFile(GC_NO_CACHE,
|
||||
@@ -631,27 +635,28 @@ TIME_OFFSET_UNITS = [('day', 86400), ('hour', 3600), ('minute', 60),
|
||||
|
||||
|
||||
def getLocalGoogleTimeOffset(testLocation='admin.googleapis.com'):
|
||||
localUTC = datetime.datetime.now(datetime.timezone.utc)
|
||||
try:
|
||||
# we disable SSL verify so we can still get time even if clock
|
||||
# is way off. This could be spoofed / MitM but we'll fail for those
|
||||
# situations everywhere else but here.
|
||||
badhttp = transport.create_http()
|
||||
badhttp.disable_ssl_certificate_validation = True
|
||||
googleUTC = dateutil.parser.parse(
|
||||
badhttp.request('https://' + testLocation, 'HEAD')[0]['date'])
|
||||
except (httplib2.ServerNotFoundError, RuntimeError, ValueError) as e:
|
||||
controlflow.system_error_exit(4, str(e))
|
||||
offset = remainder = int(abs((localUTC - googleUTC).total_seconds()))
|
||||
timeoff = []
|
||||
for tou in TIME_OFFSET_UNITS:
|
||||
uval, remainder = divmod(remainder, tou[1])
|
||||
if uval:
|
||||
timeoff.append(f'{uval} {tou[0]}{"s" if uval != 1 else ""}')
|
||||
if not timeoff:
|
||||
timeoff.append('less than 1 second')
|
||||
nicetime = ', '.join(timeoff)
|
||||
return (offset, nicetime)
|
||||
# Try with http first, if time is close (<MAX_LOCAL_GOOGLE_TIME_OFFSET seconds),
|
||||
# retry with https
|
||||
badhttp = transport.create_http()
|
||||
for prot in ['http', 'https']:
|
||||
localUTC = datetime.datetime.now(datetime.timezone.utc)
|
||||
try:
|
||||
googleUTC = dateutil.parser.parse(
|
||||
badhttp.request(f'{prot}://' + testLocation, 'HEAD')[0]['date'])
|
||||
except (httplib2.ServerNotFoundError, RuntimeError, ValueError) as e:
|
||||
controlflow.system_error_exit(4, str(e))
|
||||
offset = remainder = int(abs((localUTC - googleUTC).total_seconds()))
|
||||
if offset < MAX_LOCAL_GOOGLE_TIME_OFFSET and prot == 'http':
|
||||
continue
|
||||
timeoff = []
|
||||
for tou in TIME_OFFSET_UNITS:
|
||||
uval, remainder = divmod(remainder, tou[1])
|
||||
if uval:
|
||||
timeoff.append(f'{uval} {tou[0]}{"s" if uval != 1 else ""}')
|
||||
if not timeoff:
|
||||
timeoff.append('less than 1 second')
|
||||
nicetime = ', '.join(timeoff)
|
||||
return (offset, nicetime)
|
||||
|
||||
|
||||
def doGAMCheckForUpdates(forceCheck=False):
|
||||
@@ -777,8 +782,9 @@ def checkConnection():
|
||||
success_count = 0
|
||||
for host in hosts:
|
||||
try_count += 1
|
||||
check_line = f'Checking {host} ({try_count}/{host_count})...'
|
||||
sys.stdout.write(f'{check_line:<60}')
|
||||
ip = socket.gethostbyname(host)
|
||||
check_line = f'Checking {host} ({ip}) ({try_count}/{host_count})...'
|
||||
sys.stdout.write(f'{check_line:<80}')
|
||||
sys.stdout.flush()
|
||||
try:
|
||||
httpc.request(f'https://{host}/', 'HEAD', headers=headers)
|
||||
@@ -6572,6 +6578,15 @@ def getUserAttributes(i, cd, updateCmd):
|
||||
body.setdefault('name', {})
|
||||
body['name']['familyName'] = sys.argv[i + 1]
|
||||
i += 2
|
||||
elif myarg in ['displayname']:
|
||||
body.setdefault('name', {})
|
||||
body['name']['displayName'] = sys.argv[i + 1]
|
||||
# sigh, the API is wonky. If we set just displayName
|
||||
# we get an error. But if we also "set" fullName which is
|
||||
# really just a concat of first/last name and can't be set
|
||||
# then it works. Go figure.
|
||||
body['name']['fullName'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg in ['username', 'email', 'primaryemail'] and updateCmd:
|
||||
body['primaryEmail'] = normalizeEmailAddressOrUID(sys.argv[i + 1],
|
||||
noUid=True)
|
||||
@@ -7128,7 +7143,7 @@ def getCRMService(login_hint):
|
||||
scopes,
|
||||
'online',
|
||||
login_hint=login_hint,
|
||||
use_console_flow=not GC_Values[GC_OAUTH_BROWSER])
|
||||
open_browser=not GC_Values[GC_NO_BROWSER])
|
||||
httpc = transport.AuthorizedHttp(creds, transport.create_http())
|
||||
return getService('cloudresourcemanager', httpc), httpc
|
||||
|
||||
@@ -7284,7 +7299,7 @@ def _createClientSecretsOauth2service(httpObj, projectId, login_hint):
|
||||
'code':
|
||||
'ThisIsAnInvalidCodeOnlyBeingUsedToTestIfClientAndSecretAreValid',
|
||||
'redirect_uri':
|
||||
'urn:ietf:wg:oauth:2.0:oob',
|
||||
'http://127.0.0.1:8080/',
|
||||
'grant_type':
|
||||
'authorization_code'
|
||||
}
|
||||
@@ -7721,7 +7736,7 @@ def doUpdateProjects():
|
||||
_grantRotateRights(iam, sa_email, sa_email)
|
||||
|
||||
|
||||
def _generatePrivateKeyAndPublicCert(client_id, key_size):
|
||||
def _generatePrivateKeyAndPublicCert(client_id, key_size, b64enc_pub=True):
|
||||
print(' Generating new private key...')
|
||||
private_key = rsa.generate_private_key(public_exponent=65537,
|
||||
key_size=key_size,
|
||||
@@ -7765,6 +7780,8 @@ def _generatePrivateKeyAndPublicCert(client_id, key_size):
|
||||
backend=default_backend())
|
||||
public_cert_pem = certificate.public_bytes(
|
||||
serialization.Encoding.PEM).decode()
|
||||
if not b64enc_pub:
|
||||
return private_pem, public_cert_pem
|
||||
publicKeyData = base64.b64encode(public_cert_pem.encode())
|
||||
if isinstance(publicKeyData, bytes):
|
||||
publicKeyData = publicKeyData.decode()
|
||||
@@ -8930,10 +8947,16 @@ def doGetUserInfo(user_email=None):
|
||||
customFieldMask=customFieldMask,
|
||||
viewType=viewType)
|
||||
print(f'User: {user["primaryEmail"]}')
|
||||
if 'name' in user and 'givenName' in user['name']:
|
||||
print(f'First Name: {user["name"]["givenName"]}')
|
||||
if 'name' in user and 'familyName' in user['name']:
|
||||
print(f'Last Name: {user["name"]["familyName"]}')
|
||||
if 'name' in user:
|
||||
names = {
|
||||
'givenName': 'First Name',
|
||||
'familyName': 'Last Name',
|
||||
'fullName': 'Full Name',
|
||||
'displayName': 'Display Name',
|
||||
}
|
||||
for field, description in names.items():
|
||||
if field in user['name']:
|
||||
print(f'{description}: {user["name"][field]}')
|
||||
if 'languages' in user:
|
||||
print(f"Languages: {_formatLanguagesList(user['languages'], ',')}")
|
||||
if 'isAdmin' in user:
|
||||
@@ -9478,7 +9501,7 @@ def doUndeleteUser():
|
||||
i = 4
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if myarg in ['ou', 'org']:
|
||||
if myarg in ['ou', 'org', 'orgunit']:
|
||||
orgUnit = gapi_directory_orgunits.makeOrgUnitPathAbsolute(
|
||||
sys.argv[i + 1])
|
||||
i += 2
|
||||
@@ -9641,6 +9664,7 @@ USER_ARGUMENT_TO_PROPERTY_MAP = {
|
||||
'changepasswordatnextlogin': ['changePasswordAtNextLogin',],
|
||||
'creationtime': ['creationTime',],
|
||||
'deletiontime': ['deletionTime',],
|
||||
'displayname': ['displayName',],
|
||||
'email': ['emails',],
|
||||
'emails': ['emails',],
|
||||
'externalid': ['externalIds',],
|
||||
@@ -9682,6 +9706,7 @@ USER_ARGUMENT_TO_PROPERTY_MAP = {
|
||||
'location': ['locations',],
|
||||
'locations': ['locations',],
|
||||
'name': [
|
||||
'name.displayName',
|
||||
'name.givenName',
|
||||
'name.familyName',
|
||||
'name.fullName',
|
||||
@@ -10179,7 +10204,7 @@ def getUsersToModify(entity_type=None,
|
||||
'org_ns',
|
||||
'ou_susp',
|
||||
'org_susp',
|
||||
]:
|
||||
]:
|
||||
if entity_type in ['ou_ns', 'org_ns']:
|
||||
checkSuspended = False
|
||||
elif entity_type in ['ou_susp', 'org_susp']:
|
||||
@@ -10542,7 +10567,7 @@ def doRequestOAuth(login_hint=None, scopes=None):
|
||||
access_type='offline',
|
||||
login_hint=login_hint,
|
||||
credentials_file=GC_Values[GC_OAUTH2_TXT],
|
||||
use_console_flow=not GC_Values[GC_OAUTH_BROWSER])
|
||||
open_browser=not GC_Values[GC_NO_BROWSER])
|
||||
creds.write()
|
||||
except gam.auth.oauth.InvalidClientSecretsFileError:
|
||||
controlflow.system_error_exit(14, missing_client_secrets_message)
|
||||
@@ -10584,6 +10609,11 @@ OAUTH2_SCOPES = [
|
||||
'subscopes': ['readonly'],
|
||||
'scopes': 'https://www.googleapis.com/auth/cloud-identity.groups'
|
||||
},
|
||||
{
|
||||
'name': 'Cloud Identity - Inbound SSO Settings',
|
||||
'subscopes': ['readonly'],
|
||||
'scopes': 'https://www.googleapis.com/auth/cloud-identity.inboundsso',
|
||||
},
|
||||
{
|
||||
'name': 'Cloud Identity - OrgUnits',
|
||||
'subscopes': ['readonly'],
|
||||
@@ -10593,7 +10623,6 @@ OAUTH2_SCOPES = [
|
||||
'name': 'Cloud Identity - User Invitations',
|
||||
'subscopes': ['readonly'],
|
||||
'scopes': 'https://www.googleapis.com/auth/cloud-identity.userinvitations',
|
||||
'offByDefault': True,
|
||||
},
|
||||
{
|
||||
'name': 'Contact Delegation',
|
||||
@@ -11241,7 +11270,7 @@ def run_batch(items):
|
||||
)
|
||||
pool.close()
|
||||
pool.join()
|
||||
pool = mp_pool(num_worker_threads, init_gam_worker, maxtasksperchild=200, initargs=(1,))
|
||||
pool = mp_pool(num_worker_threads, init_gam_worker, maxtasksperchild=200, initargs=(l,))
|
||||
sys.stderr.write(
|
||||
'commit-batch - running processes finished, proceeding\n')
|
||||
continue
|
||||
@@ -11414,6 +11443,8 @@ def ProcessGAMCommand(args):
|
||||
i, encoding = getCharSet(i + 1)
|
||||
f = fileutils.open_file(filename, encoding=encoding)
|
||||
csvFile = csv.DictReader(f)
|
||||
if not csvFile.fieldnames:
|
||||
controlflow.system_error_exit(0, f'CSV file {filename} is empty')
|
||||
if (i == len(sys.argv)) or (sys.argv[i].lower() !=
|
||||
'gam') or (i + 1 == len(sys.argv)):
|
||||
controlflow.system_error_exit(
|
||||
@@ -11463,7 +11494,13 @@ def ProcessGAMCommand(args):
|
||||
gapi_cloudidentity_groups.create()
|
||||
elif argument in ['nickname', 'alias']:
|
||||
doCreateAlias()
|
||||
elif argument in ['org', 'ou']:
|
||||
elif argument in ['inboundssoprofile', 'inboundssoprofiles']:
|
||||
gapi_cloudidentity_inboundsso.create_profile()
|
||||
elif argument in ['inboundssocredential', 'inboundssocredentials']:
|
||||
gapi_cloudidentity_inboundsso.create_credentials()
|
||||
elif argument in ['inboundssoassignment', 'inboundssoassignments']:
|
||||
gapi_cloudidentity_inboundsso.create_assignment()
|
||||
elif argument in ['org', 'orgunit', 'ou']:
|
||||
gapi_directory_orgunits.create()
|
||||
elif argument == 'resource':
|
||||
gapi_directory_resource.createResourceCalendar()
|
||||
@@ -11535,10 +11572,14 @@ def ProcessGAMCommand(args):
|
||||
gapi_cloudidentity_groups.update()
|
||||
elif argument in ['nickname', 'alias']:
|
||||
doUpdateAlias()
|
||||
elif argument in ['ou', 'org']:
|
||||
elif argument in ['ou', 'org', 'orgunit']:
|
||||
gapi_directory_orgunits.update()
|
||||
elif argument == 'resource':
|
||||
gapi_directory_resource.updateResourceCalendar()
|
||||
elif argument in ['inboundssoprofile', 'inboundssoprofiles']:
|
||||
gapi_cloudidentity_inboundsso.update_profile()
|
||||
elif argument in ['inboundssoassignment', 'inboundssoasignments']:
|
||||
gapi_cloudidentity_inboundsso.update_assignment()
|
||||
elif argument == 'cros':
|
||||
gapi_directory_cros.doUpdateCros()
|
||||
elif argument == 'mobile':
|
||||
@@ -11600,7 +11641,11 @@ def ProcessGAMCommand(args):
|
||||
doGetAliasInfo()
|
||||
elif argument == 'instance':
|
||||
gapi_directory_customer.doGetCustomerInfo()
|
||||
elif argument in ['org', 'ou']:
|
||||
elif argument in ['inboundssoprofile', 'inboundssoprofiles']:
|
||||
gapi_cloudidentity_inboundsso.info_profile()
|
||||
elif argument in ['inboundssoassignment', 'inboundssoassignments']:
|
||||
gapi_cloudidentity_inboundsso.info_assignment()
|
||||
elif argument in ['ou', 'org', 'orgunit']:
|
||||
gapi_directory_orgunits.info()
|
||||
elif argument == 'resource':
|
||||
gapi_directory_resource.getResourceCalendarInfo()
|
||||
@@ -11675,8 +11720,12 @@ def ProcessGAMCommand(args):
|
||||
gapi_cloudidentity_groups.delete()
|
||||
elif argument in ['nickname', 'alias']:
|
||||
doDeleteAlias()
|
||||
elif argument == 'org':
|
||||
elif argument in ['ou', 'org', 'orgunit']:
|
||||
gapi_directory_orgunits.delete()
|
||||
elif argument in ['inboundssoprofile', 'inboundssoprofiles']:
|
||||
gapi_cloudidentity_inboundsso.delete_profile()
|
||||
elif argument in ['inboundssocredential', 'inboundssocredentials']:
|
||||
gapi_cloudidentity_inboundsso.delete_credentials()
|
||||
elif argument == 'resource':
|
||||
gapi_directory_resource.deleteResourceCalendar()
|
||||
elif argument == 'mobile':
|
||||
@@ -11768,6 +11817,12 @@ def ProcessGAMCommand(args):
|
||||
gapi_directory_groups.print_members()
|
||||
elif argument in ['cigroupmembers', 'cigroupsmembers']:
|
||||
gapi_cloudidentity_groups.print_members()
|
||||
elif argument in ['inboundssoprofile', 'inboundssoprofiles']:
|
||||
gapi_cloudidentity_inboundsso.print_show_profiles()
|
||||
elif argument in ['inboundssocredential', 'inboundssocredentials']:
|
||||
gapi_cloudidentity_inboundsso.print_show_credentials()
|
||||
elif argument in ['inboundssoassignment', 'inboundssoassignments']:
|
||||
gapi_cloudidentity_inboundsso.print_show_assignments()
|
||||
elif argument in ['orgs', 'ous']:
|
||||
gapi_directory_orgunits.print_()
|
||||
elif argument == 'privileges':
|
||||
@@ -11868,6 +11923,12 @@ def ProcessGAMCommand(args):
|
||||
gapi_licensing.show()
|
||||
elif argument in ['project', 'projects']:
|
||||
doPrintShowProjects(False)
|
||||
elif argument in ['inboundssoprofile', 'inboundssoprofiles']:
|
||||
gapi_cloudidentity_inboundsso.print_show_profiles('show')
|
||||
elif argument in ['inboundssocredential', 'inboundssocredentials']:
|
||||
gapi_cloudidentity_inboundsso.print_show_credentials('show')
|
||||
elif argument in ['inboundssoassignment', 'inboundssoassignments']:
|
||||
gapi_cloudidentity_inboundsso.print_show_assignments('show')
|
||||
elif argument in ['sakey', 'sakeys']:
|
||||
doShowServiceAccountKeys()
|
||||
elif argument in ['browsertoken', 'browsertokens']:
|
||||
|
||||
@@ -272,6 +272,7 @@ class Credentials(google.oauth2.credentials.Credentials):
|
||||
access_type='offline',
|
||||
login_hint=None,
|
||||
filename=None,
|
||||
open_browser=True,
|
||||
use_console_flow=False):
|
||||
"""Runs an OAuth Flow from client secrets to generate credentials.
|
||||
|
||||
@@ -291,8 +292,11 @@ class Credentials(google.oauth2.credentials.Credentials):
|
||||
login_hint: String, The email address that will be displayed on the Google
|
||||
login page as a hint for the user to login to the correct account.
|
||||
filename: String, the path to a file to use to save the credentials.
|
||||
use_console_flow: Boolean, True if the authentication flow should be run
|
||||
strictly from a console; False to launch a browser for authentication.
|
||||
use_console_flow: OBSOLETE: Boolean, True if the authentication flow
|
||||
should be run strictly from a console; False to launch a browser
|
||||
for authentication.
|
||||
open_browser: Boolean: whether or not GAM should try to open the browser
|
||||
automatically.
|
||||
|
||||
Returns:
|
||||
Credentials
|
||||
@@ -312,12 +316,11 @@ class Credentials(google.oauth2.credentials.Credentials):
|
||||
flow = _ShortURLFlow.from_client_config(client_config,
|
||||
scopes,
|
||||
autogenerate_code_verifier=True)
|
||||
flow_kwargs = {'access_type': access_type}
|
||||
flow_kwargs = {'access_type': access_type,
|
||||
'open_browser': open_browser}
|
||||
if login_hint:
|
||||
flow_kwargs['login_hint'] = login_hint
|
||||
|
||||
flow.run_dual(use_console_flow,
|
||||
**flow_kwargs)
|
||||
flow.run_dual(**flow_kwargs)
|
||||
return cls.from_google_oauth2_credentials(flow.credentials,
|
||||
filename=filename)
|
||||
|
||||
@@ -328,6 +331,7 @@ class Credentials(google.oauth2.credentials.Credentials):
|
||||
access_type='offline',
|
||||
login_hint=None,
|
||||
credentials_file=None,
|
||||
open_browser=True,
|
||||
use_console_flow=False):
|
||||
"""Runs an OAuth Flow from secrets stored on disk to generate credentials.
|
||||
|
||||
@@ -348,8 +352,11 @@ class Credentials(google.oauth2.credentials.Credentials):
|
||||
login page as a hint for the user to login to the correct account.
|
||||
credentials_file: String, the path to a file to use to save the
|
||||
credentials.
|
||||
use_console_flow: Boolean, True if the authentication flow should be run
|
||||
strictly from a console; False to launch a browser for authentication.
|
||||
use_console_flow: OBSOLETE: Boolean, True if the authentication flow
|
||||
should be run strictly from a console; False to launch a browser for
|
||||
authentication.
|
||||
open_browser: Boolean, whether or not GAM should try to open the browser
|
||||
directly.
|
||||
|
||||
Raises:
|
||||
InvalidClientSecretsFileError: If the client secrets file cannot be
|
||||
@@ -378,14 +385,13 @@ class Credentials(google.oauth2.credentials.Credentials):
|
||||
raise InvalidClientSecretsFileFormatError(
|
||||
f'Could not extract Client ID or Client Secret from file {client_secrets_file}'
|
||||
)
|
||||
|
||||
return cls.from_client_secrets(client_id,
|
||||
client_secret,
|
||||
scopes,
|
||||
access_type=access_type,
|
||||
login_hint=login_hint,
|
||||
filename=credentials_file,
|
||||
use_console_flow=use_console_flow)
|
||||
open_browser=open_browser)
|
||||
|
||||
def _fetch_id_token_data(self):
|
||||
"""Fetches verification details from Google for the OAuth2.0 token.
|
||||
@@ -482,7 +488,11 @@ class Credentials(google.oauth2.credentials.Credentials):
|
||||
def _locked_refresh(self, request):
|
||||
"""Refreshes the credential's access token while the file lock is held."""
|
||||
assert self._lock.is_locked
|
||||
super().refresh(request)
|
||||
try:
|
||||
super().refresh(request)
|
||||
except google.auth.exceptions.RefreshError as e:
|
||||
controlflow.system_error_exit(9, str(e))
|
||||
|
||||
|
||||
def write(self):
|
||||
"""Writes credentials to disk."""
|
||||
@@ -595,7 +605,6 @@ class _ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
|
||||
|
||||
|
||||
def run_dual(self,
|
||||
use_console_flow,
|
||||
authorization_prompt_message='',
|
||||
console_prompt_message='',
|
||||
web_success_message='',
|
||||
@@ -605,7 +614,7 @@ class _ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
|
||||
mgr = multiprocessing.Manager()
|
||||
d = mgr.dict()
|
||||
d['trailing_slash'] = redirect_uri_trailing_slash
|
||||
d['open_browser'] = use_console_flow
|
||||
d['open_browser'] = open_browser
|
||||
http_client = multiprocessing.Process(target=_wait_for_http_client,
|
||||
args=(d,))
|
||||
user_input = multiprocessing.Process(target=_wait_for_user_input,
|
||||
@@ -637,8 +646,8 @@ class _ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
|
||||
code = parsed_params.get('code', [None])[0]
|
||||
try:
|
||||
fetch_args = {'code': code}
|
||||
if GC_Values.get('GC_CA_FILE'):
|
||||
fetch_args['verify'] = GC_Values.get('GC_CA_FILE')
|
||||
if GC_Values.get(GC_CA_FILE):
|
||||
fetch_args['verify'] = GC_Values.get(GC_CA_FILE)
|
||||
self.fetch_token(**fetch_args)
|
||||
break
|
||||
except Exception as e:
|
||||
|
||||
@@ -34,7 +34,7 @@ def missing_argument_exit(argument, command):
|
||||
"""Indicate that the argument is missing for the command.
|
||||
|
||||
Args:
|
||||
argument: the missingagrument
|
||||
argument: the missing argument
|
||||
command: the base GAM command
|
||||
"""
|
||||
system_error_exit(2, f'missing argument {argument} for "{command}"')
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""Methods related to execution of GAPI requests."""
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import googleapiclient.errors
|
||||
import google.auth.exceptions
|
||||
@@ -10,7 +12,8 @@ from gam import controlflow
|
||||
from gam import display
|
||||
from gam.gapi import errors
|
||||
from gam import transport
|
||||
from gam.var import (GM_Globals, GM_CURRENT_API_SCOPES, GM_CURRENT_API_USER,
|
||||
from gam.var import (GC_Values, GM_Globals,
|
||||
GM_CURRENT_API_SCOPES, GM_CURRENT_API_USER,
|
||||
GM_EXTRA_ARGS_DICT, GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID,
|
||||
MAX_RESULTS_API_EXCEPTIONS, MESSAGE_API_ACCESS_CONFIG,
|
||||
MESSAGE_API_ACCESS_DENIED, MESSAGE_SERVICE_NOT_APPLICABLE)
|
||||
@@ -238,8 +241,13 @@ def process_page(page, items, all_items, total_items, page_message, message_attr
|
||||
page_items = page.get(items, [])
|
||||
num_page_items = len(page_items)
|
||||
total_items += num_page_items
|
||||
if all_items is not None:
|
||||
if type(all_items) is list:
|
||||
all_items.extend(page_items)
|
||||
elif all_items is not None:
|
||||
i = len(all_items)
|
||||
for item in page_items:
|
||||
all_items[str(i)] = item
|
||||
i += 1
|
||||
else:
|
||||
page_token = None
|
||||
num_page_items = 0
|
||||
@@ -273,6 +281,7 @@ def finalize_page_message(page_message):
|
||||
sys.stderr.write('\r\n')
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
def get_all_pages(service,
|
||||
function,
|
||||
items='items',
|
||||
@@ -341,13 +350,14 @@ def get_all_pages(service,
|
||||
page_token, total_items = process_page(page, items, all_items, total_items, page_message, message_attribute)
|
||||
if not page_token:
|
||||
finalize_page_message(page_message)
|
||||
if type(all_items) is not list:
|
||||
all_items = all_items.values()
|
||||
return all_items
|
||||
if page_args_in_body:
|
||||
kwargs['body']['pageToken'] = page_token
|
||||
else:
|
||||
kwargs['pageToken'] = page_token
|
||||
|
||||
|
||||
# TODO: Make this private once all execution related items that use this method
|
||||
# have been brought into this file
|
||||
def handle_oauth_token_error(e, soft_errors):
|
||||
|
||||
@@ -286,7 +286,8 @@ def printShowCrosTelemetry(mode):
|
||||
device['storageInfo']['percentDiskUsed'] = 100 - device['storageInfo']['percentDiskFree']
|
||||
for cpuStatusReport in device.get('cpuStatusReport', []):
|
||||
for tempInfo in cpuStatusReport.pop('cpuTemperatureInfo', []):
|
||||
cpuStatusReport[f"cpuTemperatureInfo.{tempInfo['label'].strip()}"] = tempInfo['temperatureCelsius']
|
||||
if 'temperatureCelsius' in tempInfo:
|
||||
cpuStatusReport[f"cpuTemperatureInfo.{tempInfo['label'].strip()}"] = tempInfo['temperatureCelsius']
|
||||
if showOrgUnitPath:
|
||||
orgUnitId = device.get('orgUnitId')
|
||||
if orgUnitId not in orgUnitIdPathMap:
|
||||
|
||||
@@ -80,7 +80,7 @@ def _parse_action(action):
|
||||
def info():
|
||||
ci = gapi_cloudidentity.build_dwd()
|
||||
customer = _get_device_customerid()
|
||||
name = _get_device_name()
|
||||
_, name = _get_deviceuser_name()
|
||||
device = gapi.call(ci.devices(), 'get', name=name, customer=customer)
|
||||
device_users = gapi.get_all_pages(ci.devices().deviceUsers(), 'list',
|
||||
'deviceUsers', parent=name, customer=customer)
|
||||
|
||||
@@ -935,6 +935,12 @@ def group_email_to_id(ci, group, i=0, count=0):
|
||||
return None
|
||||
|
||||
|
||||
def group_id_to_email(ci, group_id):
|
||||
return gapi.call(ci.groups(),
|
||||
'get',
|
||||
fields='groupKey/id',
|
||||
name=group_id).get('groupKey', {}).get('id')
|
||||
|
||||
def membership_email_to_id(ci, parent, membership, i=0, count=0):
|
||||
membership = gam.normalizeEmailAddressOrUID(membership)
|
||||
try:
|
||||
|
||||
527
src/gam/gapi/cloudidentity/inboundsso.py
Normal file
527
src/gam/gapi/cloudidentity/inboundsso.py
Normal file
@@ -0,0 +1,527 @@
|
||||
"""Methods related to Cloud Identity Inbound (Google as SP) SAML SSO"""
|
||||
from datetime import datetime
|
||||
import re
|
||||
import sys
|
||||
|
||||
import dateutil.parser
|
||||
import googleapiclient
|
||||
|
||||
import gam
|
||||
from gam.var import GC_CUSTOMER_ID, GC_Values, MY_CUSTOMER
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import fileutils
|
||||
from gam import gapi
|
||||
from gam import utils
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi import cloudidentity as gapi_cloudidentity
|
||||
from gam.gapi import directory as gapi_directory
|
||||
from gam.gapi.cloudidentity import groups as gapi_cloudidentity_groups
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
|
||||
'''returns customer in the format inboundsso requires'''
|
||||
def get_sso_customer():
|
||||
customer = GC_Values[GC_CUSTOMER_ID]
|
||||
return f'customers/{customer}'
|
||||
|
||||
|
||||
'''returns org unit in the format inboundsso requires'''
|
||||
def get_orgunit_id(orgunit):
|
||||
ou_id = gapi_directory_orgunits.getOrgUnitId(orgunit)[1]
|
||||
if ou_id.startswith('id:'):
|
||||
ou_id = ou_id[3:]
|
||||
return f'orgUnits/{ou_id}'
|
||||
|
||||
|
||||
'''build Cloud Identity API'''
|
||||
def build():
|
||||
return gapi_cloudidentity.build('cloudidentity_beta')
|
||||
|
||||
|
||||
'''parse cmd for profile create/update'''
|
||||
def parse_profile(body, i):
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'name':
|
||||
body['displayName'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'entityid':
|
||||
body.setdefault('idpConfig', {})['entityId'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'loginurl':
|
||||
body.setdefault('idpConfig', {})['singleSignOnServiceUri'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'logouturl':
|
||||
body.setdefault('idpConfig', {})['logoutRedirectUri'] = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'changepasswordurl':
|
||||
body.setdefault('idpConfig', {})['changePasswordUri'] = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg, 'gam create/update inboundssoprofile')
|
||||
return body
|
||||
|
||||
|
||||
'''convert profile nice names to unique ID'''
|
||||
def profile_displayname_to_name(displayName, ci=None):
|
||||
if displayName.lower().startswith('id:') or displayName.lower().startswith('uid:'):
|
||||
displayName = displayName.split(':', 1)[1]
|
||||
if not displayName.startswith('inboundSamlSsoProfiles/'):
|
||||
displayName = f'inboundSamlSsoProfiles/{displayName}'
|
||||
return displayName
|
||||
if not ci:
|
||||
ci = build()
|
||||
customer = get_sso_customer()
|
||||
_filter = f'customer=="{customer}"'
|
||||
profiles = gapi.get_all_pages(ci.inboundSamlSsoProfiles(),
|
||||
'list',
|
||||
'inboundSamlSsoProfiles',
|
||||
filter=_filter)
|
||||
matches = []
|
||||
for profile in profiles:
|
||||
if displayName.lower() == profile.get('displayName', '').lower():
|
||||
matches.append(profile)
|
||||
if len(matches) == 1:
|
||||
return matches[0]['name']
|
||||
if len(matches) == 0:
|
||||
controlflow.system_error_exit(3, f'No Inbound SSO profile matches the name {displayName}')
|
||||
else:
|
||||
err_text = f'Multiple profiles match {displayName}:\n\n'
|
||||
for m in matches:
|
||||
err_text += f' {m["name"]} {m["displayName"]}\n'
|
||||
controlflow.system_error_exit(3, err_text)
|
||||
|
||||
|
||||
'''get an assignment based on target'''
|
||||
def assignment_by_target(target, ci=None):
|
||||
if not ci:
|
||||
ci = build()
|
||||
group_pattern = r'^groups/[^/]+$'
|
||||
ou_pattern = r'^orgUnits/[^/]+$'
|
||||
if re.match(group_pattern, target):
|
||||
target_type = 'targetGroup'
|
||||
elif re.match(ou_pattern, target):
|
||||
target_type = 'targetOrgUnit'
|
||||
elif target.lower().startswith('group:'):
|
||||
target_type = 'targetGroup'
|
||||
group_email = target[6:]
|
||||
target = gapi_cloudidentity_groups.group_email_to_id(
|
||||
ci,
|
||||
group_email)
|
||||
elif target.lower().startswith('orgunit:'):
|
||||
target_type = 'targetOrgUnit'
|
||||
ou_name = target[8:]
|
||||
target = get_orgunit_id(ou_name)
|
||||
else:
|
||||
controlflow.system_error_exit(3, 'assignments should be prefixed with ' +
|
||||
'group:, groups/, orgunit: or orgunits/')
|
||||
customer = get_sso_customer()
|
||||
_filter = f'customer=="{customer}"'
|
||||
assignments = gapi.get_all_pages(ci.inboundSsoAssignments(),
|
||||
'list',
|
||||
'inboundSsoAssignments',
|
||||
filter=_filter)
|
||||
for assignment in assignments:
|
||||
if target_type in assignment and assignment[target_type] == target:
|
||||
return assignment
|
||||
controlflow.system_error_exit(3, f'No SSO profile assigned to {target_type} {target}')
|
||||
|
||||
|
||||
'''gam create inboundssoprofile'''
|
||||
def create_profile():
|
||||
ci = build()
|
||||
body = {
|
||||
'customer': get_sso_customer(),
|
||||
'displayName': 'SSO Profile'
|
||||
}
|
||||
body = parse_profile(body, 3)
|
||||
result = gapi.call(ci.inboundSamlSsoProfiles(),
|
||||
'create',
|
||||
body=body)
|
||||
if result.get('done'):
|
||||
print(f'Created profile {result["response"]["name"]}')
|
||||
display.print_json(result['response'])
|
||||
else:
|
||||
controlflow.system_error_exit(3, 'Create did not finish {result}')
|
||||
|
||||
'''gam print inboundssoprofiles'''
|
||||
def print_show_profiles(action='print'):
|
||||
customer = get_sso_customer()
|
||||
_filter = f'customer=="{customer}"'
|
||||
ci = build()
|
||||
todrive = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg, f'gam {action} inboundssoprofiles')
|
||||
|
||||
profiles = gapi.get_all_pages(ci.inboundSamlSsoProfiles(),
|
||||
'list',
|
||||
'inboundSamlSsoProfiles',
|
||||
filter=_filter)
|
||||
if action == 'show':
|
||||
for profile in profiles:
|
||||
display.print_json(profile)
|
||||
print()
|
||||
elif action == 'print':
|
||||
csv_rows = []
|
||||
titles = []
|
||||
for profile in profiles:
|
||||
row = utils.flatten_json(profile)
|
||||
for item in row:
|
||||
if item not in titles:
|
||||
titles.append(item)
|
||||
csv_rows.append(row)
|
||||
display.write_csv_file(csv_rows,
|
||||
titles,
|
||||
'Inbound SSO Profiles',
|
||||
todrive)
|
||||
|
||||
|
||||
'''gam update inboundssoprofile'''
|
||||
def update_profile():
|
||||
ci = build()
|
||||
name = profile_displayname_to_name(sys.argv[3], ci)
|
||||
body = {}
|
||||
body = parse_profile(body, 4)
|
||||
updateMask = ','.join(body.keys())
|
||||
result = gapi.call(ci.inboundSamlSsoProfiles(),
|
||||
'patch',
|
||||
name=name,
|
||||
updateMask=updateMask,
|
||||
body=body)
|
||||
display.print_json(result)
|
||||
|
||||
|
||||
'''gam info inboundssoprofile'''
|
||||
def info_profile(return_only=False, displayName=None, ci=None):
|
||||
if not ci:
|
||||
ci = build()
|
||||
if not displayName:
|
||||
displayName = sys.argv[3]
|
||||
name = profile_displayname_to_name(displayName, ci)
|
||||
result = gapi.call(ci.inboundSamlSsoProfiles(),
|
||||
'get',
|
||||
name=name)
|
||||
if return_only:
|
||||
return result
|
||||
display.print_json(result)
|
||||
|
||||
'''gam delete inboundssoprofile'''
|
||||
def delete_profile():
|
||||
ci = build()
|
||||
name = profile_displayname_to_name(sys.argv[3], ci)
|
||||
result = gapi.call(ci.inboundSamlSsoProfiles(),
|
||||
'delete',
|
||||
name=name)
|
||||
if result.get('done'):
|
||||
print(f'Deleted profile {name}.')
|
||||
else:
|
||||
controlflow.system_error_exit(3, 'Delete did not finish: {result}')
|
||||
|
||||
|
||||
'''gam create inboundssocredentials'''
|
||||
def create_credentials():
|
||||
allowed_sizes = [1024, 2048, 4096]
|
||||
ci = build()
|
||||
parent = None
|
||||
generate_key = False
|
||||
key_size = 2048
|
||||
pemData = None
|
||||
replace_oldest = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'profile':
|
||||
parent = sys.argv[i+1]
|
||||
parent = profile_displayname_to_name(parent, ci)
|
||||
i += 2
|
||||
elif myarg == 'pemfile':
|
||||
pemfile = sys.argv[i+1]
|
||||
pemData = fileutils.read_file(pemfile)
|
||||
i += 2
|
||||
elif myarg == 'generatekey':
|
||||
generate_key = True
|
||||
i += 1
|
||||
elif myarg == 'replaceoldest':
|
||||
replace_oldest = True
|
||||
i += 1
|
||||
elif myarg == 'keysize':
|
||||
key_size = int(sys.argv[i+1])
|
||||
if key_size not in allowed_sizes:
|
||||
controlflow.expected_argument_exit('key_size',
|
||||
allowed_sizes,
|
||||
key_size)
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg, 'gam create inboundssocredential')
|
||||
if not parent:
|
||||
controlflow.missing_argument_exit('profile', 'gam create inboundssocredential')
|
||||
if replace_oldest:
|
||||
fields='nextPageToken,idpCredentials(name,updateTime)'
|
||||
current_creds = gapi.get_all_pages(
|
||||
ci.inboundSamlSsoProfiles().idpCredentials(),
|
||||
'list',
|
||||
'idpCredentials',
|
||||
parent=parent,
|
||||
fields=fields)
|
||||
if len(current_creds) == 2:
|
||||
oldest_key = min(current_creds,
|
||||
key=lambda x:x['updateTime'])
|
||||
print(' deleting older key...')
|
||||
delete_credentials(ci=ci,
|
||||
name=oldest_key['name'])
|
||||
else:
|
||||
print(' profile has {len(current_creds)} credentials. We only replace if there are 2.')
|
||||
if generate_key:
|
||||
privKey, pemData = gam._generatePrivateKeyAndPublicCert('GAM',
|
||||
key_size,
|
||||
b64enc_pub=False)
|
||||
timestamp = datetime.now().strftime('%Y%m%d-%I%M%S')
|
||||
priv_file = f'privatekey-{timestamp}.pem'
|
||||
pub_file = f'publiccert-{timestamp}.pem'
|
||||
fileutils.write_file(priv_file, privKey)
|
||||
print(f' Wrote private key data to {priv_file}')
|
||||
fileutils.write_file(pub_file, pemData)
|
||||
print(f' Wrote public certificate to {pub_file}')
|
||||
if not pemData:
|
||||
controlflow.system_error_exit(3, 'You must either specify "pemfile <filename>" or "generate_key"')
|
||||
body = {
|
||||
'pemData': pemData,
|
||||
}
|
||||
result = gapi.call(ci.inboundSamlSsoProfiles().idpCredentials(),
|
||||
'add',
|
||||
parent=parent,
|
||||
body=body)
|
||||
if result.get('done'):
|
||||
print(f'Created credential {result["response"]["name"]}')
|
||||
display.print_json(result['response'])
|
||||
else:
|
||||
controlflow.system_error_exit(3, 'Create did not finish {result}')
|
||||
|
||||
|
||||
'''gam delete inboundssocredential'''
|
||||
def delete_credentials(ci=None, name=None):
|
||||
if not ci:
|
||||
ci = build()
|
||||
if not name:
|
||||
name = sys.argv[3]
|
||||
result = gapi.call(ci.inboundSamlSsoProfiles().idpCredentials(),
|
||||
'delete',
|
||||
name=name)
|
||||
if result.get('done'):
|
||||
print(f'Deleted credential {name}')
|
||||
else:
|
||||
controlflow.system_error_exit(3, 'Delete did not finish {result}')
|
||||
|
||||
|
||||
'''gam print inboundssocredentials'''
|
||||
def print_show_credentials(action='print'):
|
||||
ci = build()
|
||||
todrive = False
|
||||
i = 3
|
||||
profiles = []
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg in ['profile', 'profiles']:
|
||||
profiles = [profile_displayname_to_name(profile, ci)
|
||||
for profile in sys.argv[i+1].split(',')]
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg, f'gam {action} inboundssocredentials')
|
||||
if not profiles:
|
||||
customer = get_sso_customer()
|
||||
_filter = f'customer=="{customer}"'
|
||||
profiles = gapi.get_all_pages(ci.inboundSamlSsoProfiles(),
|
||||
'list',
|
||||
'inboundSamlSsoProfiles',
|
||||
fields='inboundSamlSsoProfiles/name',
|
||||
filter=_filter)
|
||||
profiles = [p['name'] for p in profiles]
|
||||
if action == 'print':
|
||||
titles = []
|
||||
csv_rows = []
|
||||
credentials = []
|
||||
for profile in profiles:
|
||||
results = gapi.get_all_pages(ci.inboundSamlSsoProfiles().idpCredentials(),
|
||||
'list',
|
||||
'idpCredentials',
|
||||
parent=profile)
|
||||
credentials.extend(results)
|
||||
if action == 'show':
|
||||
for c in credentials:
|
||||
display.print_json(c)
|
||||
print()
|
||||
elif action == 'print':
|
||||
for c in credentials:
|
||||
csv_row = utils.flatten_json(c)
|
||||
for item in csv_row:
|
||||
if item not in titles:
|
||||
titles.append(item)
|
||||
csv_rows.append(csv_row)
|
||||
display.write_csv_file(csv_rows,
|
||||
titles,
|
||||
'Inbound SSO Credentials',
|
||||
todrive)
|
||||
|
||||
'''parse command for create/update inboundssoassignment'''
|
||||
def parse_assignment(body, i, ci):
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'rank':
|
||||
body['rank'] = int(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'mode':
|
||||
mode_choices = \
|
||||
gapi.get_enum_values_minus_unspecified(
|
||||
ci._rootDesc['schemas']['InboundSsoAssignment']['properties']['ssoMode']['enum'])
|
||||
body['ssoMode'] = sys.argv[i+1].upper()
|
||||
if body['ssoMode'] not in mode_choices:
|
||||
controlflow.expected_argument_exit('mode',
|
||||
', '.join(mode_choices),
|
||||
sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg == 'profile':
|
||||
profile_name = profile_displayname_to_name(
|
||||
sys.argv[i+1],
|
||||
ci)
|
||||
body['samlSsoInfo'] = {
|
||||
'inboundSamlSsoProfile': profile_name
|
||||
}
|
||||
i += 2
|
||||
elif myarg == 'neverredirect':
|
||||
body['signInBehavior'] = {
|
||||
'redirectCondition': 'NEVER'
|
||||
}
|
||||
i += 1
|
||||
elif myarg == 'group':
|
||||
group = sys.argv[i+1]
|
||||
body['targetGroup'] = gapi_cloudidentity_groups.group_email_to_id(
|
||||
ci,
|
||||
group)
|
||||
i += 2
|
||||
elif myarg in ['ou', 'org', 'orgunit']:
|
||||
body['targetOrgUnit'] = get_orgunit_id(sys.argv[i+1])
|
||||
i += 2
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg, 'gam create/update inboundssoassignment')
|
||||
return body
|
||||
|
||||
|
||||
def update_assignment_target_names(assignment, ci, cd):
|
||||
if 'targetGroup' in assignment:
|
||||
assignment['targetGroupEmail'] = \
|
||||
gapi_cloudidentity_groups.group_id_to_email(ci,
|
||||
assignment['targetGroup'])
|
||||
elif 'targetOrgUnit' in assignment:
|
||||
ou_id = assignment['targetOrgUnit'].split('/')[1]
|
||||
assignment['targetOrgUnitPath'] = \
|
||||
gapi_directory_orgunits.orgunit_from_orgunitid(f'id:{ou_id}', cd)
|
||||
|
||||
|
||||
'''gam create inboundssoassignment'''
|
||||
def create_assignment():
|
||||
ci = build()
|
||||
cd = gapi_directory.build()
|
||||
body = {
|
||||
'customer': get_sso_customer(),
|
||||
}
|
||||
body = parse_assignment(body, 3, ci)
|
||||
result = gapi.call(ci.inboundSsoAssignments(),
|
||||
'create',
|
||||
body=body)
|
||||
if result.get('done'):
|
||||
print(f'Created assignment {result["response"]["name"]}')
|
||||
update_assignment_target_names(result['response'], ci, cd)
|
||||
display.print_json(result['response'])
|
||||
else:
|
||||
controlflow.system_error_exit(3, 'Create did not finish {result}')
|
||||
|
||||
|
||||
def get_assignment_name(name):
|
||||
if name.startswith('id:') or name.startswith('uid:'):
|
||||
name = name.split(':', 1)[1]
|
||||
if not name.startswith('inboundSsoAssignments/'):
|
||||
name = f'inboundSsoAssignments/{name}'
|
||||
return name
|
||||
|
||||
|
||||
'''gam update inboundssoassignment'''
|
||||
def update_assignment():
|
||||
ci = build()
|
||||
cd = gapi_directory.build()
|
||||
name = get_assignment_name(sys.argv[3])
|
||||
body = parse_assignment({}, 4, ci)
|
||||
updateMask = ','.join(list(body.keys()))
|
||||
result = gapi.call(ci.inboundSsoAssignments(),
|
||||
'patch',
|
||||
name=name,
|
||||
updateMask=updateMask,
|
||||
body=body)
|
||||
if result.get('done'):
|
||||
print(f'Updated assignment {result["response"]["name"]}')
|
||||
update_assignment_target_names(result['response'], ci, cd)
|
||||
display.print_json(result['response'])
|
||||
else:
|
||||
controlflow.system_error_exit(3, 'Update did not finish {result}')
|
||||
|
||||
|
||||
'''gam info inboundssoassignment'''
|
||||
def info_assignment():
|
||||
ci = build()
|
||||
cd = gapi_directory.build()
|
||||
assignment = assignment_by_target(sys.argv[3], ci)
|
||||
update_assignment_target_names(assignment, ci, cd)
|
||||
profile = assignment.get('samlSsoInfo', {}).get('inboundSamlSsoProfile')
|
||||
if profile:
|
||||
assignment['samlSsoInfo']['inboundSamlSsoProfile'] = \
|
||||
info_profile(return_only=True, displayName=f'id:{profile}', ci=ci)
|
||||
display.print_json(assignment)
|
||||
|
||||
|
||||
'''gam print inboundssoassignments'''
|
||||
def print_show_assignments(action='print'):
|
||||
ci = build()
|
||||
cd = gapi_directory.build()
|
||||
customer = get_sso_customer()
|
||||
_filter = f'customer=="{customer}"'
|
||||
todrive = False
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
controlflow.invalid_argument_exit(myarg,
|
||||
f'gam {action} inboundssoassignments')
|
||||
assignments = gapi.get_all_pages(ci.inboundSsoAssignments(),
|
||||
'list',
|
||||
'inboundSsoAssignments',
|
||||
filter=_filter)
|
||||
if action == 'show':
|
||||
for assignment in assignments:
|
||||
update_assignment_target_names(assignment, ci, cd)
|
||||
display.print_json(assignment)
|
||||
print()
|
||||
elif action == 'print':
|
||||
titles = []
|
||||
csv_rows = []
|
||||
for assignment in assignments:
|
||||
update_assignment_target_names(assignment, ci, cd)
|
||||
csv_row = utils.flatten_json(assignment)
|
||||
for item in csv_row:
|
||||
if item not in titles:
|
||||
titles.append(item)
|
||||
csv_rows.append(csv_row)
|
||||
display.write_csv_file(csv_rows,
|
||||
titles,
|
||||
'Inbound SSO Assignments',
|
||||
todrive)
|
||||
@@ -25,7 +25,7 @@ def _reduce_name(name):
|
||||
|
||||
def is_invitable_user(email):
|
||||
'''return email isInvitableUser'''
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity')
|
||||
svc = gapi_cloudidentity.build('cloudidentity')
|
||||
customer = _get_customerid()
|
||||
encoded_email = quote_plus(email)
|
||||
name = f'{customer}/userinvitations/{encoded_email}'
|
||||
@@ -35,7 +35,7 @@ def is_invitable_user(email):
|
||||
|
||||
def _generic_action(action):
|
||||
'''generic function to call actionable APIs'''
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity')
|
||||
svc = gapi_cloudidentity.build('cloudidentity')
|
||||
customer = _get_customerid()
|
||||
email = sys.argv[3].lower()
|
||||
encoded_email = quote_plus(email)
|
||||
@@ -55,7 +55,7 @@ def _generic_action(action):
|
||||
|
||||
def _generic_get(get_type):
|
||||
'''generic function to call read data APIs'''
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity')
|
||||
svc = gapi_cloudidentity.build('cloudidentity')
|
||||
customer = _get_customerid()
|
||||
email = sys.argv[3].lower()
|
||||
encoded_email = quote_plus(email)
|
||||
@@ -75,7 +75,7 @@ def bulk_is_invitable(emails):
|
||||
if response.get('isInvitableUser'):
|
||||
rows.append({'invitableUsers': request_id})
|
||||
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity')
|
||||
svc = gapi_cloudidentity.build('cloudidentity')
|
||||
customer = _get_customerid()
|
||||
todrive = False
|
||||
#batch_size = 1000
|
||||
@@ -139,7 +139,7 @@ USERINVITATION_STATE_CHOICES_MAP = {
|
||||
|
||||
def print_():
|
||||
'''gam print userinvitations'''
|
||||
svc = gapi_cloudidentity.build_dwd('cloudidentity')
|
||||
svc = gapi_cloudidentity.build('cloudidentity')
|
||||
customer = _get_customerid()
|
||||
todrive = False
|
||||
titles = ['name', 'state', 'updateTime']
|
||||
|
||||
@@ -13,12 +13,14 @@ def get_org_id():
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
crm = build()
|
||||
query = f'directorycustomerid:{GC_Values[GC_CUSTOMER_ID]}'
|
||||
orgs = gapi.get_all_pages(crm.organizations(),
|
||||
results = gapi.call(crm.organizations(),
|
||||
'search',
|
||||
'organizations',
|
||||
pageSize=1,
|
||||
fields='organizations/name',
|
||||
query=query)
|
||||
if len(orgs) < 1:
|
||||
orgs = results.get('organizations')
|
||||
if not orgs:
|
||||
# return nothing and let calling API deal with it
|
||||
# since caller knows what GCP role would serve best
|
||||
return
|
||||
return orgs[0]['name']
|
||||
return orgs[0].get('name')
|
||||
|
||||
@@ -21,36 +21,36 @@ def doGetCustomerInfo():
|
||||
'get',
|
||||
customerKey=customer_id)
|
||||
print(f'Customer ID: {customer_info["id"]}')
|
||||
print(f'Primary Domain: {customer_info["customerDomain"]}')
|
||||
fields = 'domains(creationTime,domainName,isPrimary,verified)'
|
||||
try:
|
||||
result = gapi.call(
|
||||
domains = gapi.call(
|
||||
cd.domains(),
|
||||
'get',
|
||||
'list',
|
||||
fields=fields,
|
||||
customer=customer_id,
|
||||
domainName=customer_info['customerDomain'],
|
||||
fields='verified',
|
||||
throw_reasons=[gapi.errors.ErrorReason.DOMAIN_NOT_FOUND])
|
||||
throw_reasons=[gapi.errors.ErrorReason.DOMAIN_NOT_FOUND]).get('domains', [])
|
||||
for domain in domains:
|
||||
if domain.get('isPrimary'):
|
||||
primary_domain = domain
|
||||
break
|
||||
else:
|
||||
primary_domain = {}
|
||||
except gapi.errors.GapiDomainNotFoundError:
|
||||
result = {'verified': False}
|
||||
print(f'Primary Domain Verified: {result["verified"]}')
|
||||
# If customer has changed primary domain customerCreationTime is date
|
||||
# of current primary being added, not customer create date.
|
||||
# We should also get all domains and use oldest date
|
||||
customer_creation = customer_info['customerCreationTime']
|
||||
date_format = '%Y-%m-%dT%H:%M:%S.%fZ'
|
||||
oldest = datetime.datetime.strptime(customer_creation, date_format)
|
||||
domains = gapi.get_items(cd.domains(),
|
||||
'list',
|
||||
'domains',
|
||||
customer=customer_id,
|
||||
fields='domains(creationTime)')
|
||||
primary_domain = {}
|
||||
print(f'Primary Domain: {primary_domain.get("domainName", "Unknown")}')
|
||||
print(f'Primary Domain Verified: {primary_domain.get("verified", "Unknown")}')
|
||||
# we'll assume creation time is time of oldest domain customer has
|
||||
oldest = 'Unknown'
|
||||
for domain in domains:
|
||||
creation_timestamp = int(domain['creationTime']) / 1000
|
||||
domain_creation = datetime.datetime.fromtimestamp(creation_timestamp)
|
||||
if domain_creation < oldest:
|
||||
if oldest == 'Unknown' or domain_creation < oldest:
|
||||
oldest = domain_creation
|
||||
print(f'Customer Creation Time: {oldest.strftime(date_format)}')
|
||||
customer_language = customer_info.get('language', 'Unset (defaults to en)')
|
||||
if oldest != 'Unknown':
|
||||
date_format = '%Y-%m-%dT%H:%M:%S.%fZ'
|
||||
oldest = oldest.strftime(date_format)
|
||||
print(f'Customer Creation Time: {oldest}')
|
||||
customer_language = customer_info.get('language', 'Unset or Unknown (defaults to en)')
|
||||
print(f'Default Language: {customer_language}')
|
||||
if 'postalAddress' in customer_info:
|
||||
print('Address:')
|
||||
@@ -59,7 +59,7 @@ def doGetCustomerInfo():
|
||||
print(f' {field}: {customer_info["postalAddress"][field]}')
|
||||
if 'phoneNumber' in customer_info:
|
||||
print(f'Phone: {customer_info["phoneNumber"]}')
|
||||
print(f'Admin Secondary Email: {customer_info["alternateEmail"]}')
|
||||
print(f'Admin Secondary Email: {customer_info.get("alternateEmail", "Unknown")}')
|
||||
user_counts_map = {
|
||||
'accounts:num_users': 'Total Users',
|
||||
'accounts:gsuite_basic_total_licenses': 'G Suite Basic Licenses',
|
||||
|
||||
@@ -189,7 +189,7 @@ def info(group_name=None):
|
||||
pass
|
||||
print('')
|
||||
print('Group Settings:')
|
||||
for key, value in list(basic_info.items()):
|
||||
for key, value in sorted(list(basic_info.items())):
|
||||
if (key in ['kind', 'etag']) or ((key == 'aliases') and
|
||||
(not getAliases)):
|
||||
continue
|
||||
@@ -199,7 +199,7 @@ def info(group_name=None):
|
||||
print(f' {val}')
|
||||
else:
|
||||
print(f' {key}: {value}')
|
||||
for key, value in list(settings.items()):
|
||||
for key, value in sorted(list(settings.items())):
|
||||
if key in ['kind', 'etag', 'description', 'email', 'name']:
|
||||
continue
|
||||
print(f' {key}: {value}')
|
||||
@@ -1217,6 +1217,8 @@ GROUP_SETTINGS_LIST_PATTERN = re.compile(r'([A-Z][A-Z_]+[A-Z]?)')
|
||||
def getGroupAttrValue(myarg, value, gs_object, gs_body, function):
|
||||
if myarg == 'collaborative':
|
||||
myarg = 'enablecollaborativeinbox'
|
||||
elif myarg == 'gal':
|
||||
myarg = 'includeinglobaladdresslist'
|
||||
for (attrib,
|
||||
params) in list(gs_object['schemas']['Groups']['properties'].items()):
|
||||
if attrib in ['kind', 'etag', 'email']:
|
||||
|
||||
@@ -2,6 +2,7 @@ import sys
|
||||
from time import sleep
|
||||
|
||||
import gam
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
from gam.gapi import directory as gapi_directory
|
||||
@@ -18,7 +19,7 @@ def delete():
|
||||
userKey=user_email,
|
||||
throw_reasons=[gapi_errors.ErrorReason.CONDITION_NOT_MET])
|
||||
except gam.gapi.errors.GapiConditionNotMetError as err:
|
||||
display.print_error(
|
||||
controlflow.system_error_exit(3,
|
||||
f'{err} The user {user_email} may be (or have recently been) on Google Vault Hold and thus not eligible for deletion. You can check holds with "gam user <email> show vaultholds".'
|
||||
)
|
||||
|
||||
|
||||
@@ -426,6 +426,7 @@ def showReport():
|
||||
titles = ['name', 'value', 'client_id']
|
||||
csvRows = []
|
||||
auth_apps = list()
|
||||
usage = list(usage)
|
||||
for item in usage[0]['parameters']:
|
||||
if 'name' not in item:
|
||||
continue
|
||||
|
||||
@@ -11,6 +11,7 @@ from gam import controlflow
|
||||
from gam import display
|
||||
from gam import fileutils
|
||||
from gam import gapi
|
||||
from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi import storage as gapi_storage
|
||||
from gam.gapi import directory as gapi_directory
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
@@ -976,10 +977,14 @@ def printHolds():
|
||||
for matterId in matterIds:
|
||||
i += 1
|
||||
sys.stderr.write(f'Retrieving holds for matter {matterId} ({i}/{matter_count})\n')
|
||||
holds = gapi.get_all_pages(v.matters().holds(),
|
||||
try:
|
||||
holds = gapi.get_all_pages(v.matters().holds(),
|
||||
'list',
|
||||
'holds',
|
||||
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O],
|
||||
matterId=matterId)
|
||||
except googleapiclient.errors.HttpError:
|
||||
continue
|
||||
for hold in holds:
|
||||
display.add_row_titles_to_csv_file(
|
||||
utils.flatten_json(hold, flattened={'matterId': matterId}),
|
||||
|
||||
@@ -8,7 +8,7 @@ import platform
|
||||
import re
|
||||
|
||||
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
|
||||
GAM_VERSION = '6.23'
|
||||
GAM_VERSION = '6.31'
|
||||
GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
GAM_URL = 'https://jaylee.us/gam'
|
||||
@@ -206,7 +206,7 @@ SKUS = {
|
||||
},
|
||||
'1010020030': {
|
||||
'product': 'Google-Apps',
|
||||
'aliases': ['workspacefrontline', 'workspacefrontlineworker'],
|
||||
'aliases': ['wsflw', 'workspacefrontline', 'workspacefrontlineworker'],
|
||||
'displayName': 'Workspace Frontline'
|
||||
},
|
||||
'1010340002': {
|
||||
@@ -648,6 +648,7 @@ MACOS_CODENAMES = {
|
||||
},
|
||||
11: 'Big Sur',
|
||||
12: 'Monterey',
|
||||
13: 'Ventura',
|
||||
}
|
||||
|
||||
_MICROSOFT_FORMATS_LIST = [{
|
||||
@@ -1202,6 +1203,7 @@ _DEFAULT_CHARSET = UTF8
|
||||
_FN_CLIENT_SECRETS_JSON = 'client_secrets.json'
|
||||
_FN_OAUTH2SERVICE_JSON = 'oauth2service.json'
|
||||
_FN_OAUTH2_TXT = 'oauth2.txt'
|
||||
_FN_ROOTS_PEM = 'roots.pem'
|
||||
#
|
||||
GM_Globals = {
|
||||
GM_SYSEXITRC: 0,
|
||||
@@ -1269,6 +1271,9 @@ GC_ENABLE_DASA = 'enabledasa'
|
||||
# and doRequestOAuth prints a link and waits for the verification code when
|
||||
# oauth2.txt is being created
|
||||
GC_NO_BROWSER = 'no_browser'
|
||||
# If low memory is True, GAM tries to save RAM by writing pages to disk
|
||||
# temporarily
|
||||
GC_LOW_MEMORY = 'low_memory'
|
||||
# If no_tdemail is True, writeCSVfile won't send an email
|
||||
GC_NO_TDEMAIL = 'no_tdemail'
|
||||
# oauth_browser forces usage of web server OAuth flow that proved problematic.
|
||||
@@ -1324,6 +1329,7 @@ GC_Defaults = {
|
||||
GC_DOMAIN: '',
|
||||
GC_DRIVE_DIR: '',
|
||||
GC_ENABLE_DASA: False,
|
||||
GC_LOW_MEMORY: False,
|
||||
GC_NO_BROWSER: False,
|
||||
GC_NO_TDEMAIL: False,
|
||||
GC_NO_CACHE: False,
|
||||
@@ -1343,7 +1349,7 @@ GC_Defaults = {
|
||||
GC_CSV_ROW_DROP_FILTER: '',
|
||||
GC_TLS_MIN_VERSION: TLS_MIN,
|
||||
GC_TLS_MAX_VERSION: None,
|
||||
GC_CA_FILE: None,
|
||||
GC_CA_FILE: _FN_ROOTS_PEM,
|
||||
}
|
||||
|
||||
GC_Values = {}
|
||||
@@ -1408,6 +1414,9 @@ GC_VAR_INFO = {
|
||||
GC_ENABLE_DASA: {
|
||||
GC_VAR_TYPE: GC_TYPE_BOOLEAN
|
||||
},
|
||||
GC_LOW_MEMORY: {
|
||||
GC_VAR_TYPE: GC_TYPE_BOOLEAN
|
||||
},
|
||||
GC_NO_BROWSER: {
|
||||
GC_VAR_TYPE: GC_TYPE_BOOLEAN
|
||||
},
|
||||
|
||||
1130
src/roots.pem
Normal file
1130
src/roots.pem
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[metadata]
|
||||
name = GAM for Google Workspace
|
||||
version = 6.0.23
|
||||
version = attr: gam.var.GAM_VERSION
|
||||
description = Command line management for Google Workspaces
|
||||
long_description = file: readme.md
|
||||
long_description_content_type = text/markdown
|
||||
@@ -37,6 +37,9 @@ install_requires =
|
||||
yubikey-manager >= 4.0.0
|
||||
pathvalidate
|
||||
|
||||
[options.package_data]
|
||||
* = *.pem
|
||||
|
||||
# used during pip install .[test]
|
||||
[options.extras_require]
|
||||
test = pre-commit
|
||||
|
||||
Reference in New Issue
Block a user