mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-07 07:41:38 +00:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b818002202 | ||
|
|
33065438d8 | ||
|
|
e4880a3814 | ||
|
|
a2bd409d51 | ||
|
|
352f09fad8 | ||
|
|
221a18fc51 | ||
|
|
158138f344 | ||
|
|
0d32e451e9 | ||
|
|
38780e2ba9 | ||
|
|
f3e6afbca4 | ||
|
|
cb1c7ad6fe | ||
|
|
3ee8bf2841 | ||
|
|
71d82f0f87 | ||
|
|
398a91b987 | ||
|
|
0cf17be7ca | ||
|
|
28d76c5aee | ||
|
|
04371eee53 | ||
|
|
70cf32f6ed | ||
|
|
d2495f0ed8 | ||
|
|
2d3c1e5aa8 | ||
|
|
7855447014 | ||
|
|
0028955412 | ||
|
|
c3b59a9c10 | ||
|
|
fbe1bb69c3 | ||
|
|
b2573b465b | ||
|
|
6171e3c2ef | ||
|
|
19e13a9052 | ||
|
|
b349d80be5 | ||
|
|
c5c4553ae0 | ||
|
|
ef77e6f7e9 | ||
|
|
66816863e0 | ||
|
|
7d67cd08f6 | ||
|
|
0d1b622831 | ||
|
|
a1000f7778 | ||
|
|
30fea18f93 | ||
|
|
8bbb9b6e85 | ||
|
|
4b5390f6cf | ||
|
|
941abe608a | ||
|
|
0d23175216 | ||
|
|
c9c9fbd604 | ||
|
|
9d0d7c5aa8 | ||
|
|
65b23113a9 | ||
|
|
4d3edf7203 | ||
|
|
3c5d3fc569 | ||
|
|
f0d984c693 | ||
|
|
6efe1a596f | ||
|
|
598e5cf315 | ||
|
|
3fb0b840a1 | ||
|
|
ee3fa9715c | ||
|
|
3369a7a506 | ||
|
|
a6d176c033 | ||
|
|
120d72e67a | ||
|
|
0082a5a0b9 | ||
|
|
9971cdbfb3 | ||
|
|
837bb9f84f | ||
|
|
805b196532 | ||
|
|
a23238f97f | ||
|
|
e88e2f0d7f | ||
|
|
13ed3fe80c | ||
|
|
37bfcc1251 | ||
|
|
cb7e70ce31 | ||
|
|
25006765de | ||
|
|
594164944e | ||
|
|
56ed6e8a81 | ||
|
|
159184be73 | ||
|
|
51f4f3c401 |
179
.github/workflows/build.yml
vendored
179
.github/workflows/build.yml
vendored
@@ -30,6 +30,7 @@ env:
|
||||
PYTHON_SOURCE_PATH: ${{ github.workspace }}/src/cpython
|
||||
CRYPTOGRAPHY_BUILD_OPENSSL_NO_LEGACY: 1
|
||||
CRYPTOGRAPHY_OPENSSL_NO_LEGACY: 1
|
||||
WINDOWS_CODESIGN_CERT_HASH: 590dc5bb10dfb31dbff38c0e2f9c35ef0f6d0e9e
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -76,7 +77,7 @@ jobs:
|
||||
jid: 9
|
||||
goal: build
|
||||
name: Build Arm MacOS 15
|
||||
- os: windows-2022
|
||||
- os: windows-2025
|
||||
jid: 10
|
||||
goal: build
|
||||
name: Build Intel Windows
|
||||
@@ -107,26 +108,26 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- id: auth
|
||||
name: Authenticate to Google Cloud
|
||||
uses: google-github-actions/auth@v2
|
||||
uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # 2.1.12
|
||||
with:
|
||||
workload_identity_provider: projects/297925809119/locations/global/workloadIdentityPools/gha-pool/providers/gha-provider
|
||||
service_account: github-actions-testing-for-gam@gam-project-wyo-lub-ivl.iam.gserviceaccount.com
|
||||
|
||||
- name: Cache multiple paths
|
||||
if: matrix.goal == 'build'
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@0400d5f644dc74513175e3cd8d07132dd4860809 # 4.2.4
|
||||
id: cache-python-ssl
|
||||
with:
|
||||
path: |
|
||||
cache.tar.xz
|
||||
key: gam-${{ matrix.jid }}-20250805
|
||||
key: gam-${{ matrix.jid }}-20250814
|
||||
|
||||
- name: Untar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||
@@ -136,7 +137,7 @@ jobs:
|
||||
|
||||
- name: Use pre-compiled Python for testing
|
||||
if: matrix.python != ''
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # 5.6.0
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
allow-prereleases: true
|
||||
@@ -216,14 +217,13 @@ jobs:
|
||||
|
||||
- name: MacOS import developer certificates for signing
|
||||
if: runner.os == 'macOS'
|
||||
uses: apple-actions/import-codesign-certs@v3
|
||||
uses: apple-actions/import-codesign-certs@95e84a1a18f2bdbc5c6ab9b7f4429372e4b13a8b # 5.0.3
|
||||
with:
|
||||
keychain: signing_temp
|
||||
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
|
||||
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
|
||||
|
||||
- name: Windows Configure VCode
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # 1.13.0
|
||||
if: runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||
with:
|
||||
arch: ${{ runner.arch }}
|
||||
@@ -285,7 +285,7 @@ jobs:
|
||||
echo "COMPILED_OPENSSL_VERSION=${COMPILED_OPENSSL_VERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Windows NASM Install
|
||||
uses: ilammy/setup-nasm@v1
|
||||
uses: ilammy/setup-nasm@72793074d3c8cdda771dba85f6deafe00623038b # 1.5.2
|
||||
if: matrix.goal == 'build' && runner.os == 'Windows' && runner.arch == 'X64' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||
|
||||
- name: Config OpenSSL
|
||||
@@ -462,13 +462,6 @@ jobs:
|
||||
curl -O -L "$latest_crypt_whl"
|
||||
"$PYTHON" -m pip install cryptography*.whl
|
||||
|
||||
#- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
|
||||
# - name: Compile cryptography from source (no legacy)
|
||||
# if: runner.os != 'Windows' || runner.arch != 'ARM64'
|
||||
# run: |
|
||||
# pip install --no-binary ":all:" --force cryptography
|
||||
|
||||
- name: Install pip requirements
|
||||
run: |
|
||||
echo "before anything..."
|
||||
@@ -601,6 +594,72 @@ jobs:
|
||||
echo "GAM Version ${GAMVERSION}"
|
||||
echo "GAMVERSION=${GAMVERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Install WinAppDriver
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
choco install -y winappdriver
|
||||
|
||||
- name: Enabled dev mode for WinAppDriver
|
||||
if: runner.os == 'Windows'
|
||||
shell: cmd
|
||||
run : |
|
||||
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"
|
||||
|
||||
- name: Install appium and totp tools
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
echo "Installing appium..."
|
||||
npm install -g appium
|
||||
echo "Installing totp-generator..."
|
||||
npm install "totp-generator"
|
||||
echo "Installing wdio..."
|
||||
npm install @wdio/cli
|
||||
echo "Installing appium win driver..."
|
||||
appium driver install windows
|
||||
|
||||
- name: Install Certum MSI
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$url = "https://files.certum.eu/software/SimplySignDesktop/Windows/9.3.2.67/SimplySignDesktop-9.3.2.67-64-bit-en.msi"
|
||||
$file = "SimplySignDesktop-9.3.2.67-64-bit-en.msi"
|
||||
Invoke-WebRequest $url -OutFile $file
|
||||
$log = "install.log"
|
||||
$procMain = Start-Process "msiexec" "/i `"$file`" /qn /l*! `"$log`"" -NoNewWindow -PassThru
|
||||
$procLog = Start-Process "powershell" "Get-Content -Path `"$log`" -Wait" -NoNewWindow -PassThru
|
||||
$procMain.WaitForExit()
|
||||
$procLog.Kill()
|
||||
|
||||
- name: Login to Certum
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
env:
|
||||
TOTP_SECRET: ${{ secrets.TOTP_SECRET }}
|
||||
run: |
|
||||
# disable win private firewall that interferes with appium server
|
||||
Set-NetFirewallProfile -Profile Private -Enabled False
|
||||
$appiumCmd = Get-Command appium
|
||||
$appiumPath = $appiumCmd.Path
|
||||
Start-Process -Filepath "powershell.exe" -ArgumentList "-File", $appiumPath, "--address", "127.0.0.1", "--log-level", "error"
|
||||
Start-Sleep -Seconds 10
|
||||
write-host "appium started"
|
||||
write-host "running SimplySignDesktop login..."
|
||||
node tools/ssd.mjs --log-level warn
|
||||
write-host "sleeping during login..."
|
||||
Start-Sleep 10
|
||||
|
||||
- name: Sign gam.exe
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
write-Host "Signing ${env:gam}...."
|
||||
# Always explicitely use x64 version os signtool.exe, arm64 version apparently can't
|
||||
# see Certum certs since SimplySignDesktop is x64-only today.
|
||||
Start-Process -Wait -NoNewWindow -ErrorAction Continue -FilePath 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe' -ArgumentList "sign", "/sha1", "590dc5bb10dfb31dbff38c0e2f9c35ef0f6d0e9e", "/tr", "http://time.certum.pl", "/td", "SHA256", "/fd", "SHA256", "/v", "$env:gam"
|
||||
write-Host "Verifying signature of ${env:gam}...."
|
||||
# verify signature. If we failed to sign we should fail to verify and die.
|
||||
& 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe' verify /pa /v "$env:gam"
|
||||
|
||||
- name: Configure user and service account auth
|
||||
id: configserviceaccount
|
||||
env:
|
||||
@@ -609,36 +668,8 @@ jobs:
|
||||
../.github/actions/decrypt.sh "${GAMCFGDIR}"
|
||||
$gam create signjwtserviceaccount
|
||||
|
||||
- name: Upload gam.exe Windows for signing
|
||||
if: runner.os == 'Windows' && matrix.goal != 'test'
|
||||
run: |
|
||||
export folder_number=$(date +%s)
|
||||
export folder_id=$($gam user gam-win-signer@pdl.jaylee.us add drivefile drivefilename "UPLOADING_FOR_SIGN ${folder_number}" parentid "1Xz3hYq4Mfa_r6D8EcBZHLDtHDFurYSvp" mimetype gfolder returnidonly)
|
||||
$gam user gam-win-signer@pdl.jaylee.us add drivefile localfile "$gam" parentid "$folder_id"
|
||||
$gam user gam-win-signer@pdl.jaylee.us update drivefile "$folder_id" newfilename "READYTOSIGN ${folder_number}"
|
||||
export signed_folder="SIGNED ${folder_number}"
|
||||
zero_results="gam-win-signer@pdl.jaylee.us,0"
|
||||
while true; do
|
||||
result_counts=$($gam user gam-win-signer@pdl.jaylee.us print filelist query "name = '${signed_folder}' and '1Xz3hYq4Mfa_r6D8EcBZHLDtHDFurYSvp' in parents and mimeType = 'application/vnd.google-apps.folder'" countsonly)
|
||||
echo "$result_counts"
|
||||
if [[ ! "$result_counts" =~ "$zero_results" ]]; then
|
||||
echo "looks like we have results"
|
||||
break
|
||||
fi
|
||||
echo "no results, sleeping 10..."
|
||||
sleep 10
|
||||
done
|
||||
# download signed gam.exe
|
||||
$gam user gam-win-signer@pdl.jaylee.us print filelist query "name = '${signed_folder}' and '1Xz3hYq4Mfa_r6D8EcBZHLDtHDFurYSvp' in parents and mimeType = 'application/vnd.google-apps.folder'" id | $gam csv - gam user gam-win-signer@pdl.jaylee.us print filelist query "'~~id~~' in parents and name = 'gam.exe'" id | $gam csv - gam user gam-win-signer@pdl.jaylee.us get drivefile ~id targetfolder "$gampath" targetname "signed-gam.exe" overwrite true acknowledgeabuse true
|
||||
# delete signed folder on drive
|
||||
$gam user gam-win-signer@pdl.jaylee.us print filelist query "name = '${signed_folder}' and '1Xz3hYq4Mfa_r6D8EcBZHLDtHDFurYSvp' in parents and mimeType = 'application/vnd.google-apps.folder'" id | $gam csv - gam user gam-win-signer@pdl.jaylee.us trash drivefile "~id"
|
||||
# remove unsigned gam.exe and rename signed-gam.exe
|
||||
rm -v -f "${gampath}/gam.exe"
|
||||
mv -v -f "${gampath}/signed-gam.exe" "${gampath}/gam.exe"
|
||||
#"/c/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x64/signtool.exe" verify /v /pa "$gam"
|
||||
|
||||
- name: Attest gam executable was generated from this Action
|
||||
uses: actions/attest-build-provenance@v1
|
||||
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # 2.4.0
|
||||
if: matrix.goal == 'build'
|
||||
with:
|
||||
subject-path: ${{ env.gam }}
|
||||
@@ -689,34 +720,20 @@ jobs:
|
||||
rm -v -f *.wixobj
|
||||
echo "MSI_FILENAME=${MSI_FILENAME}" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload gam MSI Windows for signing
|
||||
if: runner.os == 'Windows' && matrix.goal != 'test'
|
||||
- name: Sign GAM MSI
|
||||
if: runner.os == 'Windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
export folder_number=$(date +%s)
|
||||
export folder_id=$($gam user gam-win-signer@pdl.jaylee.us add drivefile drivefilename "UPLOADING_FOR_SIGN ${folder_number}" parentid "1Xz3hYq4Mfa_r6D8EcBZHLDtHDFurYSvp" mimetype gfolder returnidonly)
|
||||
$gam user gam-win-signer@pdl.jaylee.us add drivefile localfile "$MSI_FILENAME" parentid "$folder_id"
|
||||
rm -f -v "$MSI_FILENAME"
|
||||
$gam user gam-win-signer@pdl.jaylee.us update drivefile "$folder_id" newfilename "READYTOSIGN ${folder_number}"
|
||||
export signed_folder="SIGNED ${folder_number}"
|
||||
zero_results="gam-win-signer@pdl.jaylee.us,0"
|
||||
while true; do
|
||||
result_counts=$($gam user gam-win-signer@pdl.jaylee.us print filelist query "name = '${signed_folder}' and '1Xz3hYq4Mfa_r6D8EcBZHLDtHDFurYSvp' in parents and mimeType = 'application/vnd.google-apps.folder'" countsonly)
|
||||
echo "$result_counts"
|
||||
if [[ ! "$result_counts" =~ "$zero_results" ]]; then
|
||||
echo "looks like we have results"
|
||||
break
|
||||
fi
|
||||
echo "no results, sleeping 10..."
|
||||
sleep 10
|
||||
done
|
||||
# download signed package
|
||||
$gam user gam-win-signer@pdl.jaylee.us print filelist query "name = '${signed_folder}' and '1Xz3hYq4Mfa_r6D8EcBZHLDtHDFurYSvp' in parents and mimeType = 'application/vnd.google-apps.folder'" id | $gam csv - gam user gam-win-signer@pdl.jaylee.us print filelist query "'~~id~~' in parents and name contains '.msi'" id | $gam csv - gam user gam-win-signer@pdl.jaylee.us get drivefile ~id targetfolder "$GITHUB_WORKSPACE" targetname "$MSI_FILENAME" overwrite true acknowledgeabuse true
|
||||
# delete signed folder on drive
|
||||
$gam user gam-win-signer@pdl.jaylee.us print filelist query "name = '${signed_folder}' and '1Xz3hYq4Mfa_r6D8EcBZHLDtHDFurYSvp' in parents and mimeType = 'application/vnd.google-apps.folder'" id | $gam csv - gam user gam-win-signer@pdl.jaylee.us trash drivefile "~id"
|
||||
#"/c/Program Files (x86)/Windows Kits/10/bin/10.0.22621.0/x64/signtool.exe" verify /v /pa "$MSI_FILENAME"
|
||||
write-Host "Signing ${env:MSI_FILENAME}...."
|
||||
# Always explicitely use x64 version os signtool.exe, arm64 version apparently can't
|
||||
# see Certum certs since SimplySignDesktop is x64-only today.
|
||||
Start-Process -Wait -NoNewWindow -ErrorAction Continue -FilePath 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe' -ArgumentList "sign", "/sha1", "590dc5bb10dfb31dbff38c0e2f9c35ef0f6d0e9e", "/tr", "http://time.certum.pl", "/td", "SHA256", "/fd", "SHA256", "/v", "$env:MSI_FILENAME"
|
||||
write-Host "Verifying signature of ${env:MSI_FILENAME}...."
|
||||
# verify signature. If we failed to sign we should fail to verify and die.
|
||||
& 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe' verify /pa /v "$env:MSI_FILENAME"
|
||||
|
||||
- name: Attest that gam package files were generated from this Action
|
||||
uses: actions/attest-build-provenance@v1
|
||||
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # 2.4.0
|
||||
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.goal == 'build'
|
||||
with:
|
||||
subject-path: |
|
||||
@@ -725,7 +742,7 @@ jobs:
|
||||
gam*.msi
|
||||
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # 4.6.2
|
||||
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && matrix.goal != 'test'
|
||||
with:
|
||||
name: gam-binaries-${{ env.GAMOS }}-${{ env.arch }}-${{ matrix.jid }}
|
||||
@@ -989,16 +1006,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # 5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # 5.0.0
|
||||
|
||||
- name: VirusTotal Scan
|
||||
uses: crazy-max/ghaction-virustotal@v4
|
||||
uses: crazy-max/ghaction-virustotal@d34968c958ae283fe976efed637081b9f9dcf74f # 4.2.0
|
||||
with:
|
||||
vt_api_key: ${{ secrets.VT_API_KEY }}
|
||||
files: |
|
||||
@@ -1011,12 +1028,14 @@ jobs:
|
||||
echo "Date version: ${dateversion}"
|
||||
echo "dateversion=${dateversion}" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: "marvinpinto/action-automatic-releases@latest"
|
||||
name: Publish draft release
|
||||
- name: Publish draft release
|
||||
uses: softprops/action-gh-release@72f2c25fcb47643c292f7107632f7a47c1df5cd8 # 2.3.2
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
automatic_release_tag: "${{ steps.dateversion.outputs.dateversion }}"
|
||||
prerelease: false
|
||||
draft: true
|
||||
prerelease: false
|
||||
tag_name: "${{ steps.dateversion.outputs.dateversion }}"
|
||||
fail_on_unmatched_files: true
|
||||
files: |
|
||||
gam-binaries/*
|
||||
|
||||
|
||||
|
||||
2
.github/workflows/get-cacerts.yml
vendored
2
.github/workflows/get-cacerts.yml
vendored
@@ -16,7 +16,7 @@ defaults:
|
||||
working-directory: src
|
||||
|
||||
jobs:
|
||||
check-apis:
|
||||
check-certs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
@@ -539,6 +539,7 @@ If an item contains spaces, it should be surrounded by ".
|
||||
Must match this Python Regular Expression: [a-zA-Z0-9 '"!-]{4,30}
|
||||
<PropertyKey> ::= <String>
|
||||
<PropertyValue> ::= <String>
|
||||
<PubSubTopicName> ::= <String>
|
||||
<QueryAlert> ::= <String>
|
||||
See: https://developers.google.com/admin-sdk/alertcenter/guides/query-filters
|
||||
<QueryBrowser> ::= <String>
|
||||
@@ -1540,6 +1541,10 @@ gam show admins
|
||||
|
||||
# Alert Center
|
||||
|
||||
gam show alertsettings
|
||||
gam update alertsettings <PubsubTopicName>
|
||||
gam clear alertsettings
|
||||
|
||||
gam delete alert <AlertID>
|
||||
gam undelete alert <AlertID>
|
||||
gam info alert <AlertID> [formatjson]
|
||||
@@ -4277,19 +4282,19 @@ gam show policies
|
||||
<SSOProfileItem> ::= <SSOProfileDisplayName>|<SSOProfileName>
|
||||
<SSOProfileItemList> ::= "<SSOProfileItem>(,<SSOProfileItem>)*"
|
||||
|
||||
gam create inboundssoprofile [name <SSOProfileDisplayName>]
|
||||
gam create inboundssoprofile [saml|oidc] [name <SSOProfileDisplayName>]
|
||||
[entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
[returnnameonly]
|
||||
gam update inboundssoprofile <SSOProfileItem>
|
||||
gam update inboundssoprofile [saml|oidc] <SSOProfileItem>
|
||||
[entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
[returnnameonly]
|
||||
gam delete inboundssoprofile <SSOProfileItem>
|
||||
gam delete inboundssoprofile [saml|oidc] <SSOProfileItem>
|
||||
|
||||
gam info inboundssoprofile <SSOProfileItem>
|
||||
gam info inboundssoprofile [all|saml|oidc] <SSOProfileItem>
|
||||
[formatjson]
|
||||
gam show inboundssoprofiles
|
||||
gam show inboundssoprofiles [all|saml|oidc]
|
||||
[formatjson]
|
||||
gam print inboundssoprofiles [todrive <ToDriveAttribute>*]
|
||||
gam print inboundssoprofiles [all|saml|oidc] [todrive <ToDriveAttribute>*]
|
||||
[[formatjson [quotechar <Character>]]
|
||||
|
||||
<SSOCredentialsName> ::= [id:]inboundSamlSsoProfiles/<String>/idpCredentials/<String>
|
||||
@@ -4313,10 +4318,14 @@ gam print inboundssocredentials [profile|profiles <SSOProfileItemList>]
|
||||
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 create inboundssoassignment
|
||||
(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)
|
||||
(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)|(mode oidc_sso profile <SSOProfileName>}|(mode domain_wide_saml_if_enabled)
|
||||
[neverredirect]
|
||||
gam update inboundssoassignment <SSOAssignmentName>
|
||||
[(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)]
|
||||
(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)|(mode oidc_sso profile <SSOProfileName>}|(mode domain_wide_saml_if_enabled)
|
||||
[neverredirect]
|
||||
gam delete inboundssoassignment <SSOAssignmentSelector>
|
||||
|
||||
gam info inboundssoassignment <SSOAssignmentSelector>
|
||||
@@ -4540,11 +4549,12 @@ gam report usage customer [todrive <ToDriveAttribute>*]
|
||||
chrome|
|
||||
classroom|
|
||||
contextawareaccess|
|
||||
gplus|currents|google+|
|
||||
datastudio|
|
||||
drive|doc|docs|
|
||||
gcp|cloud|
|
||||
geminiinworkspaceapps|gemini|geminiforworkspace|
|
||||
gmail|
|
||||
gplus|currents|google+|
|
||||
groups|group|
|
||||
groupsenterprise|enterprisegroups|
|
||||
jamboard|
|
||||
|
||||
@@ -1,3 +1,61 @@
|
||||
7.19.02
|
||||
|
||||
Update `gam info user <UserItem>` to eliminate 5 second delay when getting license info.
|
||||
|
||||
Additional information:
|
||||
* See: https://github.com/GAM-team/GAM/wiki/Licenses#info-user-performance
|
||||
|
||||
7.19.01
|
||||
|
||||
Updated `gam <UserTypeEntity> print|show signature` to handle the following error
|
||||
that occurs when an alias is specified.
|
||||
```
|
||||
ERROR: 404: notFound - Requested entity was not found.
|
||||
```
|
||||
|
||||
7.19.00
|
||||
|
||||
Eliminated `drive_v3_beta` and `meet_v2_beta` from `gam.cfg` as the API betas are no longer used.
|
||||
|
||||
Updated `Meet API` scopes so that GAM can read metadata about additional Meet spaces.
|
||||
```
|
||||
[*] 34) Meet API - Manage/Display Meeting Spaces
|
||||
[*] 35) Meet API - Read Meeting Spaces metadata
|
||||
```
|
||||
|
||||
7.18.07
|
||||
|
||||
Updated `gam <UserTypeEntity> print drivelastmodification` to put `addcsvdata` columns
|
||||
after `User,id,name` rather than after the last column.
|
||||
|
||||
7.18.06
|
||||
|
||||
Updated `gam <UserTypeEntity> delete|modify messages` to improve the handling
|
||||
of the following error.
|
||||
```
|
||||
quotaExceeded - User-rate limit exceeded
|
||||
```
|
||||
|
||||
7.18.05
|
||||
|
||||
Added support for Inbound SSO OIDC profiles.
|
||||
|
||||
Currently, if you enter `gam select <SectionName>` and nothing else on the command line,
|
||||
GAM performs no action. Now, it will be treated as if you entered:
|
||||
`gam select <SectionName> save`
|
||||
|
||||
Updated to Python 3.13.7.
|
||||
|
||||
7.18.04
|
||||
|
||||
Added commands to display/manage Alert Center Pub/Sub notifications.
|
||||
* See: https://github.com/GAM-team/GAM/wiki/Alert-Center#configuring-settings
|
||||
|
||||
7.18.03
|
||||
|
||||
Updated `gam oauth create` to give a warning if the number of selected scopes will
|
||||
probably cause Google to generate a "Something went wrong" error.
|
||||
|
||||
7.18.02
|
||||
|
||||
Upgraded to OpenSSL 3.5.2.
|
||||
|
||||
100
src/cacerts.pem
100
src/cacerts.pem
@@ -433,106 +433,6 @@ pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
|
||||
mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: GoDaddy
|
||||
# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority
|
||||
# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority
|
||||
# Label: "Starfield Class 2 CA"
|
||||
# Serial: 0
|
||||
# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24
|
||||
# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a
|
||||
# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
|
||||
MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
|
||||
U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
|
||||
NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
|
||||
ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
|
||||
ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
|
||||
DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
|
||||
8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
|
||||
+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
|
||||
X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
|
||||
K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
|
||||
1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
|
||||
A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
|
||||
zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
|
||||
YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
|
||||
bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
|
||||
DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
|
||||
L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
|
||||
eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
|
||||
xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
|
||||
VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
|
||||
WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: GoDaddy
|
||||
# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority
|
||||
# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority
|
||||
# Label: "Go Daddy Class 2 CA"
|
||||
# Serial: 0
|
||||
# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67
|
||||
# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4
|
||||
# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
|
||||
MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
|
||||
YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
|
||||
MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
|
||||
ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
|
||||
MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
|
||||
ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
|
||||
PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
|
||||
wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
|
||||
EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
|
||||
avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
|
||||
YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
|
||||
sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
|
||||
/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
|
||||
IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
|
||||
YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
|
||||
ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
|
||||
OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
|
||||
TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
|
||||
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
|
||||
dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
|
||||
ReYNnyicsbkqWletNw+vHX/bvZ8=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Sectigo
|
||||
# Issuer: CN=AAA Certificate Services O=Comodo CA Limited
|
||||
# Subject: CN=AAA Certificate Services O=Comodo CA Limited
|
||||
# Label: "Comodo AAA Services root"
|
||||
# Serial: 1
|
||||
# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0
|
||||
# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49
|
||||
# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
|
||||
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
|
||||
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
|
||||
YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
|
||||
MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
|
||||
BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
|
||||
GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
|
||||
BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
|
||||
3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
|
||||
YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
|
||||
rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
|
||||
ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
|
||||
oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
|
||||
QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
|
||||
b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
|
||||
AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
|
||||
GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
|
||||
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
|
||||
G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
|
||||
l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
|
||||
smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Sectigo
|
||||
# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited
|
||||
# Subject: CN=COMODO Certification Authority O=COMODO CA Limited
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
||||
"""
|
||||
|
||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||
__version__ = '7.18.02'
|
||||
__version__ = '7.19.02'
|
||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
#pylint: disable=wrong-import-position
|
||||
@@ -3952,14 +3952,20 @@ def SetGlobalVariables():
|
||||
if checkArgumentPresent(Cmd.SELECT_CMD):
|
||||
sectionName = _selectSection()
|
||||
GM.Globals[GM.SECTION] = sectionName # Save section for inner gams
|
||||
while Cmd.ArgumentsRemaining():
|
||||
if checkArgumentPresent('save'):
|
||||
GM.Globals[GM.PARSER].set(configparser.DEFAULTSECT, GC.SECTION, sectionName)
|
||||
_writeGamCfgFile(GM.Globals[GM.PARSER], GM.Globals[GM.GAM_CFG_FILE], Act.SAVE)
|
||||
elif checkArgumentPresent('verify'):
|
||||
_verifyValues(sectionName, inputFilterSectionName, outputFilterSectionName)
|
||||
else:
|
||||
break
|
||||
# If command line is simply: gam select <SectionName>
|
||||
# assume save
|
||||
if not Cmd.ArgumentsRemaining():
|
||||
GM.Globals[GM.PARSER].set(configparser.DEFAULTSECT, GC.SECTION, sectionName)
|
||||
_writeGamCfgFile(GM.Globals[GM.PARSER], GM.Globals[GM.GAM_CFG_FILE], Act.SAVE)
|
||||
else:
|
||||
while Cmd.ArgumentsRemaining():
|
||||
if checkArgumentPresent('save'):
|
||||
GM.Globals[GM.PARSER].set(configparser.DEFAULTSECT, GC.SECTION, sectionName)
|
||||
_writeGamCfgFile(GM.Globals[GM.PARSER], GM.Globals[GM.GAM_CFG_FILE], Act.SAVE)
|
||||
elif checkArgumentPresent('verify'):
|
||||
_verifyValues(sectionName, inputFilterSectionName, outputFilterSectionName)
|
||||
else:
|
||||
break
|
||||
GM.Globals[GM.GAM_CFG_SECTION_NAME] = sectionName
|
||||
# showsections
|
||||
if checkArgumentPresent(Cmd.SHOWSECTIONS_CMD):
|
||||
@@ -4733,8 +4739,6 @@ def getAPIService(api, httpObj):
|
||||
discoveryServiceUrl=DISCOVERY_URIS[v2discovery], static_discovery=False)
|
||||
|
||||
def getService(api, httpObj):
|
||||
### Drive v3beta
|
||||
# mapDriveURL = api == API.DRIVE3 and GC.Values[GC.DRIVE_V3_BETA]
|
||||
hasLocalJSON = API.hasLocalJSON(api)
|
||||
api, version, v2discovery = API.getVersion(api)
|
||||
if api in GM.Globals[GM.CURRENT_API_SERVICES] and version in GM.Globals[GM.CURRENT_API_SERVICES][api]:
|
||||
@@ -4750,9 +4754,6 @@ def getService(api, httpObj):
|
||||
discoveryServiceUrl=DISCOVERY_URIS[v2discovery], static_discovery=False)
|
||||
GM.Globals[GM.CURRENT_API_SERVICES].setdefault(api, {})
|
||||
GM.Globals[GM.CURRENT_API_SERVICES][api][version] = service._rootDesc.copy()
|
||||
### Drive v3beta
|
||||
# if mapDriveURL:
|
||||
# setattr(service, '_baseUrl', getattr(service, '_baseUrl').replace('/v3/', '/v3beta/'))
|
||||
if GM.Globals[GM.CACHE_DISCOVERY_ONLY]:
|
||||
clearServiceCache(service)
|
||||
return service
|
||||
@@ -4794,7 +4795,10 @@ def defaultSvcAcctScopes():
|
||||
for scope in scopesList:
|
||||
if not scope.get('offByDefault'):
|
||||
saScopes.setdefault(scope['api'], [])
|
||||
saScopes[scope['api']].append(scope['scope'])
|
||||
if not isinstance(scope['scope'], list):
|
||||
saScopes[scope['api']].append(scope['scope'])
|
||||
else:
|
||||
saScopes[scope['api']].extend(scope['scope'])
|
||||
saScopes[API.DRIVEACTIVITY].append(API.DRIVE_SCOPE)
|
||||
saScopes[API.DRIVE2] = saScopes[API.DRIVE3]
|
||||
saScopes[API.DRIVETD] = saScopes[API.DRIVE3]
|
||||
@@ -5609,8 +5613,6 @@ def buildGAPIServiceObject(api, user, i=0, count=0, displayError=True):
|
||||
userEmail = getSaUser(user)
|
||||
httpObj = getHttpObj(cache=GM.Globals[GM.CACHE_DIR])
|
||||
service = getService(api, httpObj)
|
||||
if api == API.MEET_BETA:
|
||||
api = API.MEET
|
||||
credentials = getSvcAcctCredentials(api, userEmail)
|
||||
request = transportCreateRequest(httpObj)
|
||||
triesLimit = 3
|
||||
@@ -10672,9 +10674,6 @@ Select all default scopes by entering an 's'; yields [*] for default scopes, [ ]
|
||||
Unselect all scopes by entering a 'u'; yields [ ] for all scopes
|
||||
Exit without changes/authorization by entering an 'e'
|
||||
Continue to authorization by entering a 'c'
|
||||
'''
|
||||
if clientAccess:
|
||||
oauth2_menu += ''' Note, if all scopes are selected, Google will probably generate an authorization error
|
||||
'''
|
||||
menu = oauth2_menu % tuple(range(numScopes))
|
||||
selectedScopes = ['*'] * numScopes
|
||||
@@ -10710,14 +10709,21 @@ Continue to authorization by entering a 'c'
|
||||
api = a_scope['api']
|
||||
possibleScope = a_scope['scope']
|
||||
if api in currentScopes:
|
||||
for scope in currentScopes[api]:
|
||||
if scope == possibleScope:
|
||||
selectedScopes[i] = '*'
|
||||
break
|
||||
if 'readonly' in a_scope['subscopes']:
|
||||
if (scope == possibleScope+'.readonly') or (scope == a_scope.get('roscope')):
|
||||
selectedScopes[i] = 'R'
|
||||
if not isinstance(possibleScope, list):
|
||||
for scope in currentScopes[api]:
|
||||
if scope == possibleScope:
|
||||
selectedScopes[i] = '*'
|
||||
break
|
||||
if 'readonly' in a_scope['subscopes']:
|
||||
if (scope == possibleScope+'.readonly') or (scope == a_scope.get('roscope')):
|
||||
selectedScopes[i] = 'R'
|
||||
break
|
||||
else:
|
||||
for scope in possibleScope:
|
||||
if scope not in currentScopes[api]:
|
||||
break
|
||||
else:
|
||||
selectedScopes[i] = '*'
|
||||
i += 1
|
||||
else:
|
||||
i = 0
|
||||
@@ -10776,7 +10782,25 @@ Continue to authorization by entering a 'c'
|
||||
break
|
||||
sys.stdout.write(f'{ERROR_PREFIX}Invalid input "{choice}"\n')
|
||||
if selection == 'c':
|
||||
break
|
||||
if clientAccess:
|
||||
numSelectedScopes = 0
|
||||
i = 0
|
||||
for a_scope in scopesList:
|
||||
if selectedScopes[i] == '*':
|
||||
if a_scope['scope']:
|
||||
numSelectedScopes += 1
|
||||
elif selectedScopes[i] != ' ':
|
||||
numSelectedScopes += 1
|
||||
i += 1
|
||||
if numSelectedScopes <= API.NUM_CLIENT_SCOPES_ERROR_LIMIT:
|
||||
break
|
||||
# If number of scopes is > 48 we'll probably get an error
|
||||
writeStdout(Msg.NUM_SELECTED_CLIENT_SCOPES.format(numSelectedScopes, API.NUM_CLIENT_SCOPES_ERROR_LIMIT))
|
||||
choice = readStdin('\nPlease enter c to continue to authorization or any other key to amend selection: ')
|
||||
if choice and choice.lower() == 'c':
|
||||
break
|
||||
else:
|
||||
break
|
||||
return selectedScopes
|
||||
|
||||
def _localhost_to_ip():
|
||||
@@ -11467,13 +11491,14 @@ def _createOauth2serviceJSON(httpObj, projectInfo, svcAcctInfo, create_key=True)
|
||||
iam = getAPIService(API.IAM, httpObj)
|
||||
try:
|
||||
service_account = callGAPI(iam.projects().serviceAccounts(), 'create',
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.ALREADY_EXISTS],
|
||||
throwReasons=[GAPI.FAILED_PRECONDITION, GAPI.NOT_FOUND,
|
||||
GAPI.PERMISSION_DENIED, GAPI.ALREADY_EXISTS],
|
||||
name=f'projects/{projectInfo["projectId"]}',
|
||||
body={'accountId': svcAcctInfo['name'],
|
||||
'serviceAccount': {'displayName': svcAcctInfo['displayName'],
|
||||
'description': svcAcctInfo['description']}})
|
||||
entityActionPerformed([Ent.PROJECT, projectInfo['projectId'], Ent.SVCACCT, service_account['name'].rsplit('/', 1)[-1]])
|
||||
except (GAPI.notFound, GAPI.permissionDenied) as e:
|
||||
except (GAPI.failedPrecondition, GAPI.notFound, GAPI.permissionDenied) as e:
|
||||
entityActionFailedWarning([Ent.PROJECT, projectInfo['projectId']], str(e))
|
||||
return False
|
||||
except GAPI.alreadyExists as e:
|
||||
@@ -12309,8 +12334,12 @@ def checkServiceAccount(users):
|
||||
for scope in scopesList:
|
||||
if selectedScopes[i] == '*':
|
||||
saScopes.setdefault(scope['api'], [])
|
||||
saScopes[scope['api']].append(scope['scope'])
|
||||
checkScopesSet.add(scope['scope'])
|
||||
if not isinstance(scope['scope'], list):
|
||||
saScopes[scope['api']].append(scope['scope'])
|
||||
checkScopesSet.add(scope['scope'])
|
||||
else:
|
||||
saScopes[scope['api']].extend(scope['scope'])
|
||||
checkScopesSet.update(scope['scope'])
|
||||
elif selectedScopes[i] == 'R':
|
||||
saScopes.setdefault(scope['api'], [])
|
||||
if 'roscope' not in scope:
|
||||
@@ -13606,6 +13635,7 @@ REPORT_CHOICE_MAP = {
|
||||
'drive': 'drive',
|
||||
'gcp': 'gcp',
|
||||
'geminiinworkspaceapps': 'gemini_in_workspace_apps',
|
||||
'gmail': 'gmail',
|
||||
'gplus': 'gplus',
|
||||
'groups': 'groups',
|
||||
'groupsenterprise': 'groups_enterprise',
|
||||
@@ -27066,6 +27096,8 @@ def _getChatMemberEmail(cd, member):
|
||||
if member['member']['type'] == 'HUMAN':
|
||||
_, memberUid = member['member']['name'].split('/')
|
||||
member['member']['email'], _ = convertUIDtoEmailAddressWithType(f'uid:{memberUid}', cd, None, emailTypes=['user'])
|
||||
if member['member']['email'].find('@') == -1:
|
||||
member['member']['email'] = 'id:'+member['member']['email']
|
||||
elif 'groupMember' in member:
|
||||
_, memberUid = member['groupMember']['name'].split('/')
|
||||
member['groupMember']['email'], _ = convertUIDtoEmailAddressWithType(f'uid:{memberUid}', cd, None, emailTypes=['group'])
|
||||
@@ -28215,9 +28247,7 @@ def printShowChatEvents(users):
|
||||
if csvPF:
|
||||
csvPF.writeCSVfile('Chat Events')
|
||||
|
||||
def buildMeetServiceObject(api=API.MEET, user=None, i=0, count=0, entityTypeList=None):
|
||||
if GC.Values[GC.MEET_V2_BETA]:
|
||||
api = API.MEET_BETA
|
||||
def buildMeetServiceObject(api, user=None, i=0, count=0, entityTypeList=None):
|
||||
user, meet = buildGAPIServiceObject(api, user, i, count)
|
||||
kvList = [Ent.USER, user]
|
||||
if entityTypeList is not None:
|
||||
@@ -28326,7 +28356,7 @@ def createMeetSpace(users):
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, meet, kvList = buildMeetServiceObject(API.MEET, user, i, count, [Ent.MEET_SPACE, None])
|
||||
user, meet, kvList = buildMeetServiceObject(API.MEET_SPACES, user, i, count, [Ent.MEET_SPACE, None])
|
||||
if not meet:
|
||||
continue
|
||||
try:
|
||||
@@ -28365,7 +28395,7 @@ def updateMeetSpace(users):
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, meet, kvList = buildMeetServiceObject(API.MEET, user, i, count, [Ent.MEET_SPACE, name])
|
||||
user, meet, kvList = buildMeetServiceObject(API.MEET_SPACES, user, i, count, [Ent.MEET_SPACE, name])
|
||||
if not meet:
|
||||
continue
|
||||
try:
|
||||
@@ -28396,7 +28426,7 @@ def infoMeetSpace(users):
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, meet, kvList = buildMeetServiceObject(API.MEET, user, i, count, [Ent.MEET_SPACE, name])
|
||||
user, meet, kvList = buildMeetServiceObject(API.MEET_SPACES, user, i, count, [Ent.MEET_SPACE, name])
|
||||
if not meet:
|
||||
continue
|
||||
try:
|
||||
@@ -28425,7 +28455,7 @@ def endMeetConference(users):
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, meet, kvList = buildMeetServiceObject(API.MEET, user, i, count, [Ent.MEET_SPACE, name, Ent.MEET_CONFERENCE, None])
|
||||
user, meet, kvList = buildMeetServiceObject(API.MEET_SPACES, user, i, count, [Ent.MEET_SPACE, name, Ent.MEET_CONFERENCE, None])
|
||||
if not meet:
|
||||
continue
|
||||
try:
|
||||
@@ -28506,7 +28536,7 @@ def printShowMeetConferences(users):
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, meet, kvList = buildMeetServiceObject(API.MEET, user, i, count, [Ent.MEET_CONFERENCE, None])
|
||||
user, meet, kvList = buildMeetServiceObject(API.MEET_READONLY, user, i, count, [Ent.MEET_CONFERENCE, None])
|
||||
if not meet:
|
||||
continue
|
||||
try:
|
||||
@@ -28582,7 +28612,7 @@ def _printShowMeetItems(users, entityType):
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, meet, kvList = buildMeetServiceObject(API.MEET, user, i, count, [Ent.MEET_CONFERENCE, parent])
|
||||
user, meet, kvList = buildMeetServiceObject(API.MEET_READONLY, user, i, count, [Ent.MEET_CONFERENCE, parent])
|
||||
if not meet:
|
||||
continue
|
||||
if entityType == Ent.MEET_PARTICIPANT:
|
||||
@@ -32198,9 +32228,9 @@ def doUpdateMobileDevices():
|
||||
[Msg.ACTION_APPLIED, body['action']], i, count)
|
||||
except GAPI.internalError:
|
||||
entityActionFailedWarning([Ent.MOBILE_DEVICE, resourceId], Msg.DOES_NOT_EXIST, i, count)
|
||||
except (GAPI.resourceIdNotFound, GAPI.badRequest, GAPI.resourceNotFound) as e:
|
||||
except (GAPI.resourceIdNotFound, GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden) as e:
|
||||
entityActionFailedWarning([Ent.MOBILE_DEVICE, resourceId], str(e), i, count)
|
||||
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||
except GAPI.permissionDenied as e:
|
||||
ClientAPIAccessDeniedExit(str(e))
|
||||
|
||||
# gam delete mobile|mobiles <MobileDeviceEntity>
|
||||
@@ -37783,6 +37813,54 @@ def doDeleteOrUndeleteAlert():
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.permissionDenied):
|
||||
userAlertsServiceNotEnabledWarning(user)
|
||||
|
||||
def _showAlertSettings(settings):
|
||||
notifications = settings.get('notifications', [])
|
||||
count = len(notifications)
|
||||
entityPerformAction([Ent.ALERT_SETTINGS, None])
|
||||
i = 0
|
||||
for notification in notifications:
|
||||
i += 1
|
||||
printEntity([Ent.NOTIFICATION, None], i, count)
|
||||
Ind.Increment()
|
||||
showJSON(None, notification)
|
||||
Ind.Decrement()
|
||||
|
||||
# gam show alertsettings
|
||||
def doShowAlertSettings():
|
||||
checkForExtraneousArguments()
|
||||
user, ac = buildGAPIServiceObject(API.ALERTCENTER, _getAdminEmail())
|
||||
if not ac:
|
||||
return
|
||||
try:
|
||||
settings = callGAPI(ac.v1beta1(), 'getSettings',
|
||||
throwReasons=GAPI.ALERT_THROW_REASONS)
|
||||
_showAlertSettings(settings)
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.permissionDenied):
|
||||
userAlertsServiceNotEnabledWarning(user)
|
||||
|
||||
# gam update alertsettings <PubsubTopicName>
|
||||
def doUpdateAlertSettings(clear=False):
|
||||
if not clear:
|
||||
body = {'notifications':
|
||||
[{'cloudPubsubTopic': {'topicName': getString(Cmd.OB_PUBSUB_TOPIC_NAME)}}]}
|
||||
else:
|
||||
body = {'notifications': []}
|
||||
checkForExtraneousArguments()
|
||||
user, ac = buildGAPIServiceObject(API.ALERTCENTER, _getAdminEmail())
|
||||
if not ac:
|
||||
return
|
||||
try:
|
||||
settings = callGAPI(ac.v1beta1(), 'updateSettings',
|
||||
throwReasons=GAPI.ALERT_THROW_REASONS,
|
||||
body=body)
|
||||
_showAlertSettings(settings)
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.permissionDenied):
|
||||
userAlertsServiceNotEnabledWarning(user)
|
||||
|
||||
# gam clear alertsettings
|
||||
def doClearAlertSettings():
|
||||
doUpdateAlertSettings(clear=True)
|
||||
|
||||
ALERT_TIME_OBJECTS = {'createTime', 'startTime', 'endTime'}
|
||||
|
||||
def _showAlert(alert, FJQC, i=0, count=0):
|
||||
@@ -44829,21 +44907,44 @@ def waitForMailbox(entityList):
|
||||
Ind.Decrement()
|
||||
|
||||
def getUserLicenses(lic, user, skus):
|
||||
def _callbackGetLicense(_, response, exception):
|
||||
reasons_to_quit = [
|
||||
GAPI.ACCESS_NOT_CONFIGURED, # license API not turned on
|
||||
GAPI.PERMISSION_DENIED, # Admin doesn't have rights to license assignments
|
||||
GAPI.NOT_FOUND # API call succeeded, user does not have this license
|
||||
]
|
||||
|
||||
def _callbackGetLicense(request_id, response, exception):
|
||||
if exception is None:
|
||||
if response and 'skuId' in response:
|
||||
licenses.append(response['skuId'])
|
||||
del sku_calls[request_id]
|
||||
else:
|
||||
_, reason, _ = checkGAPIError(exception, softErrors=True)
|
||||
if reason in reasons_to_quit:
|
||||
del sku_calls[request_id]
|
||||
|
||||
licenses = []
|
||||
svcargs = dict([('userId', user['primaryEmail']), ('productId', None), ('skuId', None), ('fields', 'skuId')]+GM.Globals[GM.EXTRA_ARGS_LIST])
|
||||
method = getattr(lic.licenseAssignments(), 'get')
|
||||
dbatch = lic.new_batch_http_request(callback=_callbackGetLicense)
|
||||
sku_calls = {}
|
||||
for sku in skus:
|
||||
svcparms = svcargs.copy()
|
||||
svcparms['productId'] = sku[0]
|
||||
svcparms['skuId'] = sku[1]
|
||||
dbatch.add(method(**svcparms))
|
||||
dbatch.execute()
|
||||
sku_id = sku[1]
|
||||
svcparms['skuId'] = sku_id
|
||||
sku_calls[sku_id] = method(**svcparms)
|
||||
try_count = 0
|
||||
while sku_calls:
|
||||
try_count += 1
|
||||
dbatch = lic.new_batch_http_request(callback=_callbackGetLicense)
|
||||
for sku_id, sku_call in sku_calls.items():
|
||||
dbatch.add(sku_call, request_id=sku_id)
|
||||
dbatch.execute()
|
||||
if sku_calls:
|
||||
if try_count >= 5:
|
||||
# give up and return what we have
|
||||
return licenses
|
||||
time.sleep(5)
|
||||
return licenses
|
||||
|
||||
USER_NAME_PROPERTY_PRINT_ORDER = [
|
||||
@@ -46328,9 +46429,30 @@ def checkCIUserIsInvitable(users):
|
||||
return
|
||||
csvPF.writeCSVfile('Invitable Users')
|
||||
|
||||
INBOUNDSSO_INPUT_MODE_CHOICE_MAP = {
|
||||
'saml': 'saml',
|
||||
'samlsso': 'saml',
|
||||
'oidc': 'oidc',
|
||||
'oidcsso': 'oidc',
|
||||
}
|
||||
|
||||
INBOUNDSSO_OUTPUT_MODE_CHOICE_MAP = {
|
||||
'all': 'all',
|
||||
'saml': 'saml',
|
||||
'samlsso': 'saml',
|
||||
'oidc': 'oidc',
|
||||
'oidcsso': 'oidc',
|
||||
}
|
||||
|
||||
INBOUNDSSO_ALL_SAML = {'all', 'saml'}
|
||||
INBOUNDSSO_ALL_OIDC = {'all', 'oidc'}
|
||||
|
||||
INBOUNDSSO_MODE_CHOICE_MAP = {
|
||||
'ssooff': 'SSO_OFF',
|
||||
'saml': 'SAML_SSO',
|
||||
'samlsso': 'SAML_SSO',
|
||||
'oidc': 'OIDC_SSO',
|
||||
'oidcsso': 'OIDC_SSO',
|
||||
'domainwidesamlifenabled': 'DOMAIN_WIDE_SAML_IF_ENABLED'
|
||||
}
|
||||
|
||||
@@ -46340,29 +46462,49 @@ def getCIOrgunitID(cd, orgunit):
|
||||
ou_id = ou_id[3:]
|
||||
return f'orgUnits/{ou_id}'
|
||||
|
||||
def _getInboundSSOProfiles(ci):
|
||||
def _getInboundSSOProfiles(ci, mode):
|
||||
customer = normalizeChannelCustomerID(GC.Values[GC.CUSTOMER_ID])
|
||||
try:
|
||||
return callGAPIpages(ci.inboundSamlSsoProfiles(), 'list', 'inboundSamlSsoProfiles',
|
||||
throwReasons=GAPI.CISSO_LIST_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
filter=f'customer=="{customer}"')
|
||||
except (GAPI.notFound, GAPI.domainNotFound, GAPI.domainCannotUseApis,
|
||||
GAPI.forbidden, GAPI.badRequest, GAPI.invalid,
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning([Ent.INBOUND_SSO_PROFILE, customer], str(e))
|
||||
return []
|
||||
profiles = []
|
||||
if mode in INBOUNDSSO_ALL_SAML:
|
||||
try:
|
||||
profiles.extend(callGAPIpages(ci.inboundSamlSsoProfiles(), 'list', 'inboundSamlSsoProfiles',
|
||||
throwReasons=GAPI.CISSO_LIST_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
filter=f'customer=="{customer}"'))
|
||||
except (GAPI.notFound, GAPI.domainNotFound, GAPI.domainCannotUseApis,
|
||||
GAPI.forbidden, GAPI.badRequest, GAPI.invalid,
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning([Ent.INBOUND_SSO_PROFILE, customer], str(e))
|
||||
if mode in INBOUNDSSO_ALL_OIDC:
|
||||
try:
|
||||
profiles.extend(callGAPIpages(ci.inboundOidcSsoProfiles(), 'list', 'inboundOidcSsoProfiles',
|
||||
throwReasons=GAPI.CISSO_LIST_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
filter=f'customer=="{customer}"'))
|
||||
except (GAPI.notFound, GAPI.domainNotFound, GAPI.domainCannotUseApis,
|
||||
GAPI.forbidden, GAPI.badRequest, GAPI.invalid,
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning([Ent.INBOUND_SSO_PROFILE, customer], str(e))
|
||||
return profiles
|
||||
|
||||
def _convertInboundSSOProfileDisplaynameToName(ci=None, displayName=''):
|
||||
def _convertInboundSSOProfileDisplaynameToName(ci, mode, displayName='',
|
||||
entityType=Ent.INBOUND_SSO_PROFILE):
|
||||
if displayName.lower().startswith('id:') or displayName.lower().startswith('uid:'):
|
||||
displayName = displayName.split(':', 1)[1]
|
||||
if not displayName.startswith('inboundSamlSsoProfiles/'):
|
||||
displayName = f'inboundSamlSsoProfiles/{displayName}'
|
||||
if mode == 'all':
|
||||
if not (displayName.startswith('inboundSamlSsoProfiles/') and
|
||||
displayName.startswith('inboundOidcSsoProfiles/')):
|
||||
displayName = f'inboundSamlSsoProfiles/{displayName}'
|
||||
elif mode == 'saml':
|
||||
if not displayName.startswith('inboundSamlSsoProfiles/'):
|
||||
displayName = f'inboundSamlSsoProfiles/{displayName}'
|
||||
else:
|
||||
if not displayName.startswith('inboundOidcSsoProfiles/'):
|
||||
displayName = f'inboundOidcSsoProfiles/{displayName}'
|
||||
return displayName
|
||||
if not ci:
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
profiles = _getInboundSSOProfiles(ci)
|
||||
profiles = _getInboundSSOProfiles(ci, mode)
|
||||
matches = []
|
||||
for profile in profiles:
|
||||
if displayName.lower() == profile.get('displayName', '').lower():
|
||||
@@ -46370,30 +46512,50 @@ def _convertInboundSSOProfileDisplaynameToName(ci=None, displayName=''):
|
||||
if len(matches) == 1:
|
||||
return matches[0]['name']
|
||||
if len(matches) == 0:
|
||||
usageErrorExit(Msg.NO_SSO_PROFILE_MATCHES.format(displayName))
|
||||
errMsg = Msg.MULTIPLE_SSO_PROFILES_MATCH.format(displayName)
|
||||
for m in matches:
|
||||
errMsg += f' {m["name"]} {m["displayName"]}\n'
|
||||
usageErrorExit(errMsg)
|
||||
errMsg = Msg.NO_SSO_PROFILE_MATCHES.format(displayName)
|
||||
else:
|
||||
errMsg = Msg.MULTIPLE_SSO_PROFILES_MATCH.format(displayName)
|
||||
for m in matches:
|
||||
errMsg += f' {m["name"]} {m["displayName"]}\n'
|
||||
entityActionFailedWarning([entityType, None], errMsg)
|
||||
return None
|
||||
|
||||
def _getInboundSSOProfileArguments(body):
|
||||
def _getInboundSSOProfileArguments(body, mode):
|
||||
returnNameOnly = False
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'name':
|
||||
body['displayName'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'entityid':
|
||||
body.setdefault('idpConfig', {})['entityId'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'loginurl':
|
||||
body.setdefault('idpConfig', {})['singleSignOnServiceUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'logouturl':
|
||||
body.setdefault('idpConfig', {})['logoutRedirectUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'changepasswordurl':
|
||||
body.setdefault('idpConfig', {})['changePasswordUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'returnnameonly':
|
||||
returnNameOnly = True
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if mode == 'saml':
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'name':
|
||||
body['displayName'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'entityid':
|
||||
body.setdefault('idpConfig', {})['entityId'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'loginurl':
|
||||
body.setdefault('idpConfig', {})['singleSignOnServiceUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'logouturl':
|
||||
body.setdefault('idpConfig', {})['logoutRedirectUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'changepasswordurl':
|
||||
body.setdefault('idpConfig', {})['changePasswordUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'returnnameonly':
|
||||
returnNameOnly = True
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
else:
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'name':
|
||||
body['displayName'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'issueruri':
|
||||
body.setdefault('idpConfig', {})['issuerUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'changepasswordurl':
|
||||
body.setdefault('idpConfig', {})['changePasswordUri'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'clientid':
|
||||
body.setdefault('rpConfig', {})['clientId'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'clientsecret':
|
||||
body.setdefault('rpConfig', {})['clientSecret'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'returnnameonly':
|
||||
returnNameOnly = True
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
return (returnNameOnly, body)
|
||||
|
||||
def _showInboundSSOProfile(profile, FJQC, i=0, count=0):
|
||||
@@ -46424,18 +46586,24 @@ def _processInboundSSOProfileResult(result, returnNameOnly, kvlist, function):
|
||||
else:
|
||||
writeStdout('inProgress\n')
|
||||
|
||||
# gam create inboundssoprofile [name <SSOProfileName>]
|
||||
def _getInboundSSOModeService(ci):
|
||||
mode = getChoice(INBOUNDSSO_INPUT_MODE_CHOICE_MAP, defaultChoice='saml', mapChoice=True)
|
||||
service = ci.inboundSamlSsoProfiles() if mode == 'saml' else ci.inboundOidcSsoProfiles()
|
||||
return (mode, service)
|
||||
|
||||
# gam create inboundssoprofile [saml|oidc] [name <SSOProfileName>]
|
||||
# [entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
# [returnnameonly]
|
||||
def doCreateInboundSSOProfile():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
mode, service = _getInboundSSOModeService(ci)
|
||||
body = {'customer': normalizeChannelCustomerID(GC.Values[GC.CUSTOMER_ID]),
|
||||
'displayName': 'SSO Profile'
|
||||
}
|
||||
returnNameOnly, body = _getInboundSSOProfileArguments(body)
|
||||
returnNameOnly, body = _getInboundSSOProfileArguments(body, mode)
|
||||
kvlist = [Ent.INBOUND_SSO_PROFILE, body['displayName']]
|
||||
try:
|
||||
result = callGAPI(ci.inboundSamlSsoProfiles(), 'create',
|
||||
result = callGAPI(service, 'create',
|
||||
throwReasons=GAPI.CISSO_CREATE_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
@@ -46446,16 +46614,19 @@ def doCreateInboundSSOProfile():
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
|
||||
# gam update inboundssoprofile <SSOProfileItem>
|
||||
# gam update inboundssoprofile [saml|oidc] <SSOProfileItem>
|
||||
# [entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
# [returnnameonly]
|
||||
def doUpdateInboundSSOProfile():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, getString(Cmd.OB_STRING))
|
||||
returnNameOnly, body = _getInboundSSOProfileArguments({})
|
||||
mode, service = _getInboundSSOModeService(ci)
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, mode, getString(Cmd.OB_STRING))
|
||||
if not name:
|
||||
return
|
||||
returnNameOnly, body = _getInboundSSOProfileArguments({}, mode)
|
||||
kvlist = [Ent.INBOUND_SSO_PROFILE, name]
|
||||
try:
|
||||
result = callGAPI(ci.inboundSamlSsoProfiles(), 'patch',
|
||||
result = callGAPI(service, 'patch',
|
||||
throwReasons=GAPI.CISSO_UPDATE_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
@@ -46468,14 +46639,17 @@ def doUpdateInboundSSOProfile():
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
|
||||
# gam delete inboundssoprofile <SSOProfileItem>
|
||||
# gam delete inboundssoprofile [saml|oidc] <SSOProfileItem>
|
||||
def doDeleteInboundSSOProfile():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, getString(Cmd.OB_STRING))
|
||||
mode, service = _getInboundSSOModeService(ci)
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, mode, getString(Cmd.OB_STRING))
|
||||
if not name:
|
||||
return
|
||||
checkForExtraneousArguments()
|
||||
kvlist = [Ent.INBOUND_SSO_PROFILE, name]
|
||||
try:
|
||||
result = callGAPI(ci.inboundSamlSsoProfiles(), 'delete',
|
||||
result = callGAPI(service, 'delete',
|
||||
throwReasons=GAPI.CISSO_UPDATE_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
@@ -46488,33 +46662,54 @@ def doDeleteInboundSSOProfile():
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
|
||||
def _getInboundSSOProfile(ci, name):
|
||||
def _getInboundSSOProfileByName(ci, mode, name):
|
||||
notFound = False
|
||||
kvlist = [Ent.INBOUND_SSO_PROFILE, name]
|
||||
try:
|
||||
return callGAPI(ci.inboundSamlSsoProfiles(), 'get',
|
||||
throwReasons=GAPI.CISSO_GET_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
name=name)
|
||||
except GAPI.notFound:
|
||||
if mode in INBOUNDSSO_ALL_SAML:
|
||||
try:
|
||||
return callGAPI(ci.inboundSamlSsoProfiles(), 'get',
|
||||
throwReasons=GAPI.CISSO_GET_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
name=name)
|
||||
except GAPI.notFound:
|
||||
notFound = True
|
||||
except (GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
|
||||
GAPI.badRequest, GAPI.invalid, GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
if mode in INBOUNDSSO_ALL_OIDC:
|
||||
try:
|
||||
return callGAPI(ci.inboundOidcSsoProfiles(), 'get',
|
||||
throwReasons=GAPI.CISSO_GET_THROW_REASONS,
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
bailOnInternalError=True,
|
||||
name=name)
|
||||
except GAPI.notFound:
|
||||
notFound = True
|
||||
except (GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
|
||||
GAPI.badRequest, GAPI.invalid, GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
if notFound:
|
||||
entityActionFailedWarning(kvlist, Msg.DOES_NOT_EXIST)
|
||||
except (GAPI.domainNotFound, GAPI.domainCannotUseApis, GAPI.forbidden,
|
||||
GAPI.badRequest, GAPI.invalid, GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
return None
|
||||
|
||||
# gam info inboundssoprofile <SSOProfileItem> [formatjson]
|
||||
# gam info inboundssoprofile [all|saml|oidc] <SSOProfileItem> [formatjson]
|
||||
def doInfoInboundSSOProfile():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, getString(Cmd.OB_STRING))
|
||||
mode = getChoice(INBOUNDSSO_OUTPUT_MODE_CHOICE_MAP, defaultChoice='all', mapChoice=True)
|
||||
name = getString(Cmd.OB_STRING)
|
||||
FJQC = FormatJSONQuoteChar(formatJSONOnly=True)
|
||||
profile = _getInboundSSOProfile(ci, name)
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, mode, name)
|
||||
if not name:
|
||||
return
|
||||
mode = 'saml' if name.startswith('inboundSamlSsoProfiles/') else 'oidc'
|
||||
profile = _getInboundSSOProfileByName(ci, mode, name)
|
||||
if profile:
|
||||
_showInboundSSOProfile(profile, FJQC)
|
||||
|
||||
# gam show inboundssoprofile
|
||||
# gam show inboundssoprofile [all|saml|oidc]
|
||||
# [formatjson]
|
||||
# gam print inboundssoprofile [todrive <ToDriveAttribute>*]
|
||||
# gam print inboundssoprofile [all|saml|oidc] [todrive <ToDriveAttribute>*]
|
||||
# [[formatjson [quotechar <Character>]]
|
||||
def doPrintShowInboundSSOProfiles():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
@@ -46522,6 +46717,7 @@ def doPrintShowInboundSSOProfiles():
|
||||
csvPF = CSVPrintFile(['name']) if Act.csvFormat() else None
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
cfilter = f'customer=="{customer}"'
|
||||
mode = getChoice(INBOUNDSSO_OUTPUT_MODE_CHOICE_MAP, defaultChoice='all', mapChoice=True)
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if csvPF and myarg == 'todrive':
|
||||
@@ -46530,7 +46726,7 @@ def doPrintShowInboundSSOProfiles():
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
if csvPF:
|
||||
printGettingAllAccountEntities(Ent.INBOUND_SSO_PROFILE, cfilter)
|
||||
profiles = _getInboundSSOProfiles(ci)
|
||||
profiles = _getInboundSSOProfiles(ci, mode)
|
||||
if not csvPF:
|
||||
count = len(profiles)
|
||||
if not FJQC.formatJSON:
|
||||
@@ -46600,6 +46796,7 @@ def _processInboundSSOCredentialsResult(result, kvlist, function):
|
||||
# (pemfile <FileName>)|(generatekey [keysize 1024|2048|4096]) [replaceolddest]
|
||||
def doCreateInboundSSOCredential():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
mode = 'saml'
|
||||
profile = None
|
||||
generateKey = replaceOldest = False
|
||||
keySize = 2048
|
||||
@@ -46607,7 +46804,11 @@ def doCreateInboundSSOCredential():
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'profile':
|
||||
profile = _convertInboundSSOProfileDisplaynameToName(ci, getString(Cmd.OB_STRING))
|
||||
profile = _convertInboundSSOProfileDisplaynameToName(ci, mode,
|
||||
getString(Cmd.OB_STRING),
|
||||
Ent.INBOUND_SSO_CREDENTIALS)
|
||||
if not profile:
|
||||
return
|
||||
elif myarg == 'pemfile':
|
||||
pemData = readFile(getString(Cmd.OB_FILE_NAME))
|
||||
elif myarg == 'generatekey':
|
||||
@@ -46711,15 +46912,24 @@ def doPrintShowInboundSSOCredentials():
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
csvPF = CSVPrintFile(['name']) if Act.csvFormat() else None
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
mode = 'saml'
|
||||
profiles = []
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg in {'profile', 'profiles'}:
|
||||
profiles = [_convertInboundSSOProfileDisplaynameToName(ci, profile) for profile in getString(Cmd.OB_STRING_LIST).split(',')]
|
||||
errors = 0
|
||||
for profile in getEntityList(Cmd.OB_STRING_LIST, shlexSplit=True):
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, mode, profile, Ent.INBOUND_SSO_CREDENTIALS)
|
||||
if name:
|
||||
profiles.append(name)
|
||||
else:
|
||||
errors += 1
|
||||
if errors:
|
||||
return
|
||||
else:
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
if not profiles:
|
||||
profiles = [p['name'] for p in _getInboundSSOProfiles(ci)]
|
||||
profiles = [p['name'] for p in _getInboundSSOProfiles(ci, mode)]
|
||||
count = len(profiles)
|
||||
i = 0
|
||||
for profile in profiles:
|
||||
@@ -46812,6 +47022,7 @@ def _getInboundSSOAssignmentByTarget(ci, cd, target):
|
||||
usageErrorExit(Msg.NO_SSO_PROFILE_ASSIGNED.format(targetType, target))
|
||||
|
||||
def _getInboundSSOAssignmentArguments(ci, cd, body):
|
||||
mode = None
|
||||
rank = 0
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
@@ -46819,9 +47030,19 @@ def _getInboundSSOAssignmentArguments(ci, cd, body):
|
||||
rank = getInteger(minVal=1)
|
||||
elif myarg == 'mode':
|
||||
body['ssoMode'] = getChoice(INBOUNDSSO_MODE_CHOICE_MAP, mapChoice=True)
|
||||
elif myarg == 'profile':
|
||||
body['samlSsoInfo'] = {'inboundSamlSsoProfile':
|
||||
_convertInboundSSOProfileDisplaynameToName(ci, getString(Cmd.OB_STRING))}
|
||||
if body['ssoMode'] == 'SAML_SSO':
|
||||
mode = 'saml'
|
||||
profile = 'inboundSamlSsoProfile'
|
||||
elif body['ssoMode'] == 'OIDC_SSO':
|
||||
mode = 'oidc'
|
||||
profile = 'inboundOidcSsoProfile'
|
||||
elif mode and myarg == 'profile':
|
||||
name = _convertInboundSSOProfileDisplaynameToName(ci, mode,
|
||||
getString(Cmd.OB_STRING),
|
||||
Ent.INBOUND_SSO_ASSIGNMENT)
|
||||
if not name:
|
||||
return None
|
||||
body['samlSsoInfo'] = {profile: name}
|
||||
elif myarg == 'neverredirect':
|
||||
body['signInBehavior'] = {'redirectCondition': 'NEVER'}
|
||||
elif myarg == 'group':
|
||||
@@ -46832,7 +47053,7 @@ def _getInboundSSOAssignmentArguments(ci, cd, body):
|
||||
unknownArgumentExit()
|
||||
if 'ssoMode' not in body:
|
||||
missingArgumentExit('mode')
|
||||
if body['ssoMode'] == 'SAML_SSO' and 'samlSsoInfo' not in body:
|
||||
if mode and 'samlSsoInfo' not in body:
|
||||
missingArgumentExit('profile')
|
||||
if 'targetGroup' in body:
|
||||
if 'targetOrgUnit' in body:
|
||||
@@ -46870,13 +47091,17 @@ def _processInboundSSOAssignmentResult(result, kvlist, ci, cd, function):
|
||||
else:
|
||||
entityActionPerformedMessage(kvlist, Msg.ACTION_IN_PROGRESS.format(f'{function} inboundssoassignment'))
|
||||
|
||||
# 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 create inboundssoassignment
|
||||
# (group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)
|
||||
# (mode sso_off)|(mode saml_sso profile <SSOProfileItem>)|(mode oidc_sso profile <SSOProfileName>}|(mode domain_wide_saml_if_enabled)
|
||||
# [neverredirect]
|
||||
def doCreateInboundSSOAssignment():
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
body = {'customer': normalizeChannelCustomerID(GC.Values[GC.CUSTOMER_ID])}
|
||||
body = _getInboundSSOAssignmentArguments(ci, cd, body)
|
||||
if not body:
|
||||
return
|
||||
kvlist = [Ent.INBOUND_SSO_ASSIGNMENT, body['customer']]
|
||||
try:
|
||||
result = callGAPI(ci.inboundSsoAssignments(), 'create',
|
||||
@@ -46890,8 +47115,10 @@ def doCreateInboundSSOAssignment():
|
||||
GAPI.systemError, GAPI.permissionDenied, GAPI.internalError, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning(kvlist, str(e))
|
||||
|
||||
# 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 update inboundssoassignment <SSOAssignmentName>
|
||||
# [(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)]
|
||||
# (mode sso_off)|(mode saml_sso profile <SSOProfileItem>)|(mode oidc_sso profile <SSOProfileName>}|(mode domain_wide_saml_if_enabled)
|
||||
# [neverredirect]
|
||||
def doUpdateInboundSSOAssignment():
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_INBOUND_SSO)
|
||||
@@ -46948,14 +47175,20 @@ def doInfoInboundSSOAssignment():
|
||||
return
|
||||
name = assignment.get('samlSsoInfo', {}).get('inboundSamlSsoProfile')
|
||||
if name:
|
||||
profile = _getInboundSSOProfile(ci, name)
|
||||
profile = _getInboundSSOProfileByName(ci, 'saml', name)
|
||||
if profile:
|
||||
assignment['samlSsoInfo']['inboundSamlSsoProfile'] = profile
|
||||
else:
|
||||
name = assignment.get('oidcSsoInfo', {}).get('inboundOidcSsoProfile')
|
||||
if name:
|
||||
profile = _getInboundSSOProfileByName(ci, 'oidc', name)
|
||||
if profile:
|
||||
assignment['oidcSsoInfo']['inboundOidcSsoProfile'] = profile
|
||||
_showInboundSSOAssignment(assignment, FJQC, ci, cd)
|
||||
|
||||
# gam show inboundssoassignment
|
||||
# gam show inboundssoassignments
|
||||
# [formatjson]
|
||||
# gam print inboundssoassignment [todrive <ToDriveAttribute>*]
|
||||
# gam print inboundssoassignments [todrive <ToDriveAttribute>*]
|
||||
# [[formatjson [quotechar <Character>]]
|
||||
def doPrintShowInboundSSOAssignments():
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
@@ -58086,11 +58319,11 @@ def printShowDrivelastModifications(users):
|
||||
DLP.Finalize(fileIdEntity)
|
||||
if csvPF:
|
||||
sortTitles = ['User', 'id', 'name'] if fileIdEntity.get('shareddrive') else ['User']
|
||||
if addCSVData:
|
||||
sortTitles.extend(sorted(addCSVData.keys()))
|
||||
sortTitles.extend(['lastModifiedFileId', 'lastModifiedFileName',
|
||||
'lastModifiedFileMimeType', 'lastModifiedFilePath',
|
||||
'lastModifyingUser', 'lastModifiedTime'])
|
||||
if addCSVData:
|
||||
sortTitles.extend(sorted(addCSVData.keys()))
|
||||
csvPF.SetTitles(sortTitles)
|
||||
csvPF.SetSortAllTitles()
|
||||
pagesFields = getItemFieldsFromFieldsList('files', fieldsList)
|
||||
@@ -71070,7 +71303,7 @@ def _processMessagesThreads(users, entityType):
|
||||
try:
|
||||
callGAPI(gmail.users().messages(), function,
|
||||
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.INVALID_MESSAGE_ID, GAPI.INVALID, GAPI.INVALID_ARGUMENT,
|
||||
GAPI.FAILED_PRECONDITION, GAPI.PERMISSION_DENIED],
|
||||
GAPI.FAILED_PRECONDITION, GAPI.PERMISSION_DENIED, GAPI.QUOTA_EXCEEDED],
|
||||
userId='me', body=body)
|
||||
for messageId in body['ids']:
|
||||
mcount += 1
|
||||
@@ -71080,7 +71313,7 @@ def _processMessagesThreads(users, entityType):
|
||||
csvPF.WriteRow({'User': user, entityHeader: messageId, 'action': Act.Performed()})
|
||||
except GAPI.serviceNotAvailable:
|
||||
mcount += bcount
|
||||
except (GAPI.invalid, GAPI.invalidArgument, GAPI.permissionDenied) as e:
|
||||
except (GAPI.invalid, GAPI.invalidArgument, GAPI.permissionDenied, GAPI.quotaExceeded) as e:
|
||||
_processMessageFailed(user, idsList, f'{str(e)} ({mcount+1}-{mcount+bcount}/{jcount})')
|
||||
mcount += bcount
|
||||
except GAPI.invalidMessageId:
|
||||
@@ -71093,7 +71326,8 @@ def _processMessagesThreads(users, entityType):
|
||||
|
||||
_GMAIL_ERROR_REASON_TO_MESSAGE_MAP = {GAPI.NOT_FOUND: Msg.DOES_NOT_EXIST,
|
||||
GAPI.INVALID_MESSAGE_ID: Msg.INVALID_MESSAGE_ID,
|
||||
GAPI.FAILED_PRECONDITION: Msg.FAILED_PRECONDITION}
|
||||
GAPI.FAILED_PRECONDITION: Msg.FAILED_PRECONDITION,
|
||||
GAPI.QUOTA_EXCEEDED: Msg.QUOTA_EXCEEDED}
|
||||
|
||||
def _callbackProcessMessage(request_id, _, exception):
|
||||
ri = request_id.splitlines()
|
||||
@@ -71104,7 +71338,9 @@ def _processMessagesThreads(users, entityType):
|
||||
csvPF.WriteRow({'User': ri[RI_ENTITY], entityHeader: ri[RI_ITEM], 'action': Act.Performed()})
|
||||
else:
|
||||
http_status, reason, message = checkGAPIError(exception)
|
||||
_processMessageFailed(ri[RI_ENTITY], ri[RI_ITEM], getHTTPError(_GMAIL_ERROR_REASON_TO_MESSAGE_MAP, http_status, reason, message), int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||
_processMessageFailed(ri[RI_ENTITY], ri[RI_ITEM],
|
||||
getHTTPError(_GMAIL_ERROR_REASON_TO_MESSAGE_MAP, http_status, reason, message),
|
||||
int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||
|
||||
def _batchProcessMessagesThreads(service, function, user, jcount, messageIds, **kwargs):
|
||||
svcargs = dict([('userId', 'me'), ('id', None), ('fields', '')]+list(kwargs.items())+GM.Globals[GM.EXTRA_ARGS_LIST])
|
||||
@@ -71202,7 +71438,9 @@ def _processMessagesThreads(users, entityType):
|
||||
continue
|
||||
if parameters['messageEntity'] is None:
|
||||
if parameters['maxToProcess'] and jcount > parameters['maxToProcess']:
|
||||
entityNumEntitiesActionNotPerformedWarning([Ent.USER, user], entityType, jcount, Msg.COUNT_N_EXCEEDS_MAX_TO_PROCESS_M.format(jcount, Act.ToPerform(), parameters['maxToProcess']), i, count)
|
||||
entityNumEntitiesActionNotPerformedWarning([Ent.USER, user], entityType, jcount,
|
||||
Msg.COUNT_N_EXCEEDS_MAX_TO_PROCESS_M.format(jcount, Act.ToPerform(), parameters['maxToProcess']),
|
||||
i, count)
|
||||
continue
|
||||
if not parameters['doIt']:
|
||||
entityNumEntitiesActionNotPerformedWarning([Ent.USER, user], entityType, jcount, Msg.USE_DOIT_ARGUMENT_TO_PERFORM_ACTION, i, count)
|
||||
@@ -74451,7 +74689,7 @@ def printShowSignature(users):
|
||||
try:
|
||||
if selection is None:
|
||||
sendas = callGAPI(gmail.users().settings().sendAs(), 'get',
|
||||
throwReasons=GAPI.GMAIL_THROW_REASONS,
|
||||
throwReasons=GAPI.GMAIL_THROW_REASONS+[GAPI.NOT_FOUND],
|
||||
userId='me', sendAsEmail=user)
|
||||
else:
|
||||
results = callGAPIitems(gmail.users().settings().sendAs(), 'list', 'sendAs',
|
||||
@@ -74480,6 +74718,8 @@ def printShowSignature(users):
|
||||
if field in sendas[item]:
|
||||
row[f'smtpMsa{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}{field}'] = sendas[item][field]
|
||||
csvPF.WriteRowTitles(row)
|
||||
except GAPI.notFound as e:
|
||||
entityActionFailedWarning([Ent.USER, user], str(e), i, count)
|
||||
except GAPI.serviceNotAvailable:
|
||||
userGmailServiceNotEnabledWarning(user, i, count)
|
||||
if csvPF:
|
||||
@@ -77170,7 +77410,8 @@ MAIN_COMMANDS_WITH_OBJECTS = {
|
||||
),
|
||||
'clear':
|
||||
(Act.CLEAR,
|
||||
{Cmd.ARG_CONTACT: doClearDomainContacts,
|
||||
{Cmd.ARG_ALERTSETTINGS: doClearAlertSettings,
|
||||
Cmd.ARG_CONTACT: doClearDomainContacts,
|
||||
}
|
||||
),
|
||||
'close':
|
||||
@@ -77491,6 +77732,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
|
||||
Cmd.ARG_ADMIN: doPrintShowAdmins,
|
||||
Cmd.ARG_ALERT: doPrintShowAlerts,
|
||||
Cmd.ARG_ALERTFEEDBACK: doPrintShowAlertFeedback,
|
||||
Cmd.ARG_ALERTSETTINGS: doShowAlertSettings,
|
||||
Cmd.ARG_BROWSER: doPrintShowBrowsers,
|
||||
Cmd.ARG_BROWSERTOKEN: doPrintShowBrowserTokens,
|
||||
Cmd.ARG_BUILDING: doPrintShowBuildings,
|
||||
@@ -77583,6 +77825,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
|
||||
'update':
|
||||
(Act.UPDATE,
|
||||
{Cmd.ARG_ADMINROLE: doCreateUpdateAdminRoles,
|
||||
Cmd.ARG_ALERTSETTINGS: doUpdateAlertSettings,
|
||||
Cmd.ARG_ALIAS: doCreateUpdateAliases,
|
||||
Cmd.ARG_BROWSER: doUpdateBrowsers,
|
||||
Cmd.ARG_BUILDING: doUpdateBuilding,
|
||||
|
||||
@@ -433,106 +433,6 @@ pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1
|
||||
mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: GoDaddy
|
||||
# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority
|
||||
# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority
|
||||
# Label: "Starfield Class 2 CA"
|
||||
# Serial: 0
|
||||
# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24
|
||||
# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a
|
||||
# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl
|
||||
MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp
|
||||
U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw
|
||||
NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE
|
||||
ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp
|
||||
ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3
|
||||
DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf
|
||||
8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN
|
||||
+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0
|
||||
X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa
|
||||
K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA
|
||||
1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G
|
||||
A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR
|
||||
zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0
|
||||
YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD
|
||||
bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w
|
||||
DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3
|
||||
L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D
|
||||
eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl
|
||||
xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp
|
||||
VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY
|
||||
WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: GoDaddy
|
||||
# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority
|
||||
# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority
|
||||
# Label: "Go Daddy Class 2 CA"
|
||||
# Serial: 0
|
||||
# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67
|
||||
# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4
|
||||
# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh
|
||||
MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE
|
||||
YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3
|
||||
MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo
|
||||
ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg
|
||||
MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN
|
||||
ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA
|
||||
PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w
|
||||
wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi
|
||||
EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY
|
||||
avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+
|
||||
YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE
|
||||
sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h
|
||||
/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5
|
||||
IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj
|
||||
YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD
|
||||
ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy
|
||||
OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P
|
||||
TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ
|
||||
HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER
|
||||
dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf
|
||||
ReYNnyicsbkqWletNw+vHX/bvZ8=
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Sectigo
|
||||
# Issuer: CN=AAA Certificate Services O=Comodo CA Limited
|
||||
# Subject: CN=AAA Certificate Services O=Comodo CA Limited
|
||||
# Label: "Comodo AAA Services root"
|
||||
# Serial: 1
|
||||
# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0
|
||||
# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49
|
||||
# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb
|
||||
MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow
|
||||
GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj
|
||||
YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL
|
||||
MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE
|
||||
BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM
|
||||
GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP
|
||||
ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua
|
||||
BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe
|
||||
3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4
|
||||
YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR
|
||||
rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm
|
||||
ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU
|
||||
oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF
|
||||
MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v
|
||||
QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t
|
||||
b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF
|
||||
AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q
|
||||
GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz
|
||||
Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2
|
||||
G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi
|
||||
l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3
|
||||
smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg==
|
||||
-----END CERTIFICATE-----
|
||||
|
||||
# Operating CA: Sectigo
|
||||
# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited
|
||||
# Subject: CN=COMODO Certification Authority O=COMODO CA Limited
|
||||
|
||||
@@ -75,8 +75,8 @@ IAM_CREDENTIALS = 'iamcredentials'
|
||||
KEEP = 'keep'
|
||||
LICENSING = 'licensing'
|
||||
LOOKERSTUDIO = 'datastudio'
|
||||
MEET = 'meet'
|
||||
MEET_BETA = 'meetbeta'
|
||||
MEET_SPACES = 'meet'
|
||||
MEET_READONLY = 'meetreadonly'
|
||||
OAUTH2 = 'oauth2'
|
||||
ORGPOLICY = 'orgpolicy'
|
||||
PEOPLE = 'people'
|
||||
@@ -119,6 +119,7 @@ USERINFO_PROFILE_SCOPE = 'https://www.googleapis.com/auth/userinfo.profile' # pr
|
||||
VAULT_SCOPES = ['https://www.googleapis.com/auth/ediscovery', 'https://www.googleapis.com/auth/ediscovery.readonly']
|
||||
REQUIRED_SCOPES = [USERINFO_EMAIL_SCOPE, USERINFO_PROFILE_SCOPE]
|
||||
REQUIRED_SCOPES_SET = set(REQUIRED_SCOPES)
|
||||
NUM_CLIENT_SCOPES_ERROR_LIMIT = 48
|
||||
#
|
||||
JWT_APIS = {
|
||||
ACCESSCONTEXTMANAGER: [CLOUD_PLATFORM_SCOPE],
|
||||
@@ -266,8 +267,8 @@ _INFO = {
|
||||
KEEP: {'name': 'Keep API', 'version': 'v1', 'v2discovery': True},
|
||||
LICENSING: {'name': 'License Manager API', 'version': 'v1', 'v2discovery': True},
|
||||
LOOKERSTUDIO: {'name': 'Looker Studio API', 'version': 'v1', 'v2discovery': True, 'localjson': True},
|
||||
MEET: {'name': 'Meet API', 'version': 'v2', 'v2discovery': True},
|
||||
MEET_BETA: {'name': 'Meet API Beta', 'version': 'v2beta', 'v2discovery': True, 'localjson': True, 'mappedAPI': MEET},
|
||||
MEET_SPACES: {'name': 'Meet API - Manage/Display Meeting Spaces', 'version': 'v2', 'v2discovery': True},
|
||||
MEET_READONLY: {'name': 'Meet API - Read Meeting Spaces metadata', 'version': 'v2', 'v2discovery': True, 'mappedAPI': MEET_SPACES},
|
||||
OAUTH2: {'name': 'OAuth2 API', 'version': 'v2', 'v2discovery': False},
|
||||
ORGPOLICY: {'name': 'Organization Policy API', 'version': 'v2', 'v2discovery': True},
|
||||
PEOPLE: {'name': 'People API', 'version': 'v1', 'v2discovery': True},
|
||||
@@ -688,11 +689,15 @@ _SVCACCT_SCOPES = [
|
||||
'api': LOOKERSTUDIO,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/datastudio'},
|
||||
{'name': 'Meet API',
|
||||
'api': MEET,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/meetings.space.created',
|
||||
'roscope': 'https://www.googleapis.com/auth/meetings.space.readonly'},
|
||||
{'name': 'Meet API - Manage/Display Meeting Spaces',
|
||||
'api': MEET_SPACES,
|
||||
'subscopes': [],
|
||||
'scope': ['https://www.googleapis.com/auth/meetings.space.created',
|
||||
'https://www.googleapis.com/auth/meetings.space.settings']},
|
||||
{'name': 'Meet API - Read Meeting Spaces metadata',
|
||||
'api': MEET_READONLY,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/meetings.space.readonly'},
|
||||
{'name': 'OAuth2 API',
|
||||
'api': OAUTH2,
|
||||
'subscopes': [],
|
||||
@@ -843,4 +848,3 @@ def findAPIforScope(scopesList):
|
||||
if not requiredAPIs:
|
||||
requiredAPIs = scopesList
|
||||
return ' or '.join(requiredAPIs)
|
||||
|
||||
|
||||
@@ -153,8 +153,6 @@ DOMAIN = 'domain'
|
||||
DRIVE_DIR = 'drive_dir'
|
||||
# When retrieving lists of Drive files/folders from API, how many should be retrieved in each chunk
|
||||
DRIVE_MAX_RESULTS = 'drive_max_results'
|
||||
# Use Drive V3 beta
|
||||
DRIVE_V3_BETA = 'drive_v3_beta'
|
||||
# When processing email messages in batches, how many should be processed in each batch
|
||||
EMAIL_BATCH_SIZE = 'email_batch_size'
|
||||
# Enable Delegated Admin Service Account
|
||||
@@ -177,8 +175,6 @@ INTER_BATCH_WAIT = 'inter_batch_wait'
|
||||
LICENSE_MAX_RESULTS = 'license_max_results'
|
||||
# License SKUs to process
|
||||
LICENSE_SKUS = 'license_skus'
|
||||
# Use Meet V2 beta
|
||||
MEET_V2_BETA = 'meet_v2_beta'
|
||||
# When retrieving lists of Google Group members from API, how many should be retrieved in each chunk
|
||||
MEMBER_MAX_RESULTS = 'member_max_results'
|
||||
# CI API Group members max page size when view=BASIC
|
||||
@@ -379,7 +375,6 @@ Defaults = {
|
||||
DRIVE_DIR: '',
|
||||
ENFORCE_EXPANSIVE_ACCESS: TRUE,
|
||||
DRIVE_MAX_RESULTS: '1000',
|
||||
DRIVE_V3_BETA: FALSE,
|
||||
EMAIL_BATCH_SIZE: '50',
|
||||
ENABLE_DASA: FALSE,
|
||||
ENABLE_GCLOUD_REAUTH: FALSE,
|
||||
@@ -390,7 +385,6 @@ Defaults = {
|
||||
INTER_BATCH_WAIT: '0',
|
||||
LICENSE_MAX_RESULTS: '100',
|
||||
LICENSE_SKUS: '',
|
||||
MEET_V2_BETA: FALSE,
|
||||
MEMBER_MAX_RESULTS: '200',
|
||||
MEMBER_MAX_RESULTS_CI_BASIC: '1000',
|
||||
MEMBER_MAX_RESULTS_CI_FULL: '500',
|
||||
@@ -547,7 +541,6 @@ VAR_INFO = {
|
||||
DRIVE_DIR: {VAR_TYPE: TYPE_DIRECTORY, VAR_ENVVAR: 'GAMDRIVEDIR'},
|
||||
ENFORCE_EXPANSIVE_ACCESS: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
DRIVE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 1000)},
|
||||
DRIVE_V3_BETA: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
EMAIL_BATCH_SIZE: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 100)},
|
||||
ENABLE_DASA: {VAR_TYPE: TYPE_BOOLEAN, VAR_SIGFILE: 'enabledasa.txt', VAR_SFFT: (FALSE, TRUE)},
|
||||
ENABLE_GCLOUD_REAUTH: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
@@ -558,7 +551,6 @@ VAR_INFO = {
|
||||
INTER_BATCH_WAIT: {VAR_TYPE: TYPE_FLOAT, VAR_LIMITS: (0.0, 60.0)},
|
||||
LICENSE_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (10, 1000)},
|
||||
LICENSE_SKUS: {VAR_TYPE: TYPE_STRING, VAR_LIMITS: (0, None)},
|
||||
MEET_V2_BETA: {VAR_TYPE: TYPE_BOOLEAN},
|
||||
MEMBER_MAX_RESULTS: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 200)},
|
||||
MEMBER_MAX_RESULTS_CI_BASIC: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 1000)},
|
||||
MEMBER_MAX_RESULTS_CI_FULL: {VAR_TYPE: TYPE_INTEGER, VAR_LIMITS: (1, 500)},
|
||||
|
||||
@@ -411,6 +411,7 @@ class GamCLArgs():
|
||||
ARG_ALERTFEEDBACK = 'alertfeedback'
|
||||
ARG_ALERTFEEDBACKS = 'alertfeedbacks'
|
||||
ARG_ALERTSFEEDBACK = 'alertsfeedback'
|
||||
ARG_ALERTSETTINGS = 'alertsettings'
|
||||
ARG_ALIAS = 'alias'
|
||||
ARG_ALIASES = 'aliases'
|
||||
ARG_ALIASDOMAIN = 'aliasdomain'
|
||||
@@ -1013,6 +1014,7 @@ class GamCLArgs():
|
||||
OB_PROJECT_ID_ENTITY = 'ProjectIDEntity'
|
||||
OB_PROPERTY_KEY = 'PropertyKey'
|
||||
OB_PROPERTY_VALUE = 'PropertyValue'
|
||||
OB_PUBSUB_TOPIC_NAME = 'PubSubTopicName'
|
||||
OB_QUERY = 'Query'
|
||||
OB_QUERY_ITEM = 'QueryItem'
|
||||
OB_QUERY_LIST = 'QueryList'
|
||||
|
||||
@@ -54,6 +54,7 @@ class GamEntity():
|
||||
ALERT_ID = 'alri'
|
||||
ALERT_FEEDBACK = 'alfb'
|
||||
ALERT_FEEDBACK_ID = 'alfi'
|
||||
ALERT_SETTINGS = 'alrs'
|
||||
ALIAS = 'alia'
|
||||
ALIAS_EMAIL = 'alie'
|
||||
ALIAS_TARGET = 'alit'
|
||||
@@ -285,10 +286,11 @@ class GamEntity():
|
||||
MIMETYPE = 'mime'
|
||||
MOBILE_DEVICE = 'mobi'
|
||||
NAME = 'name'
|
||||
NONEDITABLE_ALIAS = 'neal'
|
||||
NOTE = 'note'
|
||||
NOTE_ACL = 'nota'
|
||||
NOTES_ACLS = 'naac'
|
||||
NONEDITABLE_ALIAS = 'neal'
|
||||
NOTIFICATION = 'noti'
|
||||
OAUTH2_TXT_FILE = 'oaut'
|
||||
OAUTH2SERVICE_JSON_FILE = 'oau2'
|
||||
ORGANIZATIONAL_UNIT = 'orgu'
|
||||
@@ -414,6 +416,7 @@ class GamEntity():
|
||||
ALERT_ID: ['Alert IDs', 'Alert ID'],
|
||||
ALERT_FEEDBACK: ['Alert Feedbacks', 'Alert Feedback'],
|
||||
ALERT_FEEDBACK_ID: ['Alert Feedback IDs', 'Alert Feedback ID'],
|
||||
ALERT_SETTINGS: ['Alert Settings', 'Alert Settings'],
|
||||
ALIAS: ['Aliases', 'Alias'],
|
||||
ALIAS_EMAIL: ['Alias Emails', 'Alias Email'],
|
||||
ALIAS_TARGET: ['Alias Targets', 'Alias Target'],
|
||||
@@ -645,10 +648,11 @@ class GamEntity():
|
||||
MIMETYPE: ['MIME Types', 'MIME Type'],
|
||||
MOBILE_DEVICE: ['Mobile Devices', 'Mobile Device'],
|
||||
NAME: ['Names', 'Name'],
|
||||
NONEDITABLE_ALIAS: ['Non-Editable Aliases', 'Non-Editable Alias'],
|
||||
NOTE: ['Notes', 'Note'],
|
||||
NOTE_ACL: ['Note ACLs', 'Note ACL'],
|
||||
NOTES_ACLS: ["'Note's ACLs", "Note's ACLs"],
|
||||
NONEDITABLE_ALIAS: ['Non-Editable Aliases', 'Non-Editable Alias'],
|
||||
NOTIFICATION: ['Notifications', 'Notification'],
|
||||
OAUTH2_TXT_FILE: ['Client OAuth2 File', 'Client OAuth2 File'],
|
||||
OAUTH2SERVICE_JSON_FILE: ['Service Account OAuth2 File', 'Service Account OAuth2 File'],
|
||||
ORGANIZATIONAL_UNIT: ['Organizational Units', 'Organizational Unit'],
|
||||
|
||||
@@ -433,6 +433,7 @@ NO_SVCACCT_ACCESS_ALLOWED = 'No Service Account Access allowed'
|
||||
NO_TRANSFER_LACK_OF_DISK_SPACE = 'Transfer not performed due to lack of target drive space.'
|
||||
NO_USAGE_PARAMETERS_DATA_AVAILABLE = 'No usage parameters data available.'
|
||||
NO_USER_COUNTS_DATA_AVAILABLE = 'No User counts data available.'
|
||||
NUM_SELECTED_CLIENT_SCOPES = '\n{0} scopes are selected, if more than {1} scopes are selected, Google will probably generate a "Something went wrong" error\n'
|
||||
OAUTH2_GO_TO_LINK_MESSAGE = """
|
||||
Go to the following link in a browser on this computer or on another computer:
|
||||
|
||||
@@ -464,6 +465,7 @@ PROCESSING_ITEM_N = '{0},0,Processing item {1}\n'
|
||||
PROCESSING_ITEM_N_OF_M = '{0},0,Processing item {1}/{2}\n'
|
||||
PROFILE_PHOTO_NOT_FOUND = 'Profile photo not found'
|
||||
PROFILE_PHOTO_IS_DEFAULT = 'Profile photo is default'
|
||||
QUOTA_EXCEEDED = 'Quota exceeded'
|
||||
REASON_ONLY_VALID_WITH_CONTENTRESTRICTIONS_READONLY_TRUE = 'reason only valid with contentrestrictions readonly true'
|
||||
REAUTHENTICATION_IS_NEEDED = 'Reauthentication is needed, please run\n\ngam oauth create'
|
||||
RECOMMEND_RUNNING_GAM_ROTATE_SAKEY = 'Recommend running "gam rotate sakey" to get a new key\n'
|
||||
|
||||
125
src/tools/ssd.mjs
Normal file
125
src/tools/ssd.mjs
Normal file
@@ -0,0 +1,125 @@
|
||||
// Node.js script that implements an Appium client which will launch
|
||||
// Simply Sign Desktop app and log a user in. Once logged in it should
|
||||
// be possible to use tools like signtool.exe to sign Windows EXE/MSI files
|
||||
// with the Certum certificate.
|
||||
|
||||
import { Key, remote } from 'webdriverio';
|
||||
import { exec } from 'child_process';
|
||||
import { TOTP } from 'totp-generator';
|
||||
|
||||
async function screenshot(driver, filename) {
|
||||
// uncomment to save .png screenshots
|
||||
//await driver.saveScreenshot(filename);
|
||||
return
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function executeCommand(command) {
|
||||
try {
|
||||
let { stdout, stderr } = await exec(command);
|
||||
return stdout;
|
||||
} catch (error) {
|
||||
console.error(`Error executing command: ${command}`);
|
||||
console.error(`Error details: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function runSSD() {
|
||||
const opts = {
|
||||
port: 4723,
|
||||
logLevel: "silent",
|
||||
capabilities: {
|
||||
platformName: "Windows",
|
||||
"appium:app": "C:\\Program Files\\Certum\\SimplySign Desktop\\SimplySignDesktop.exe",
|
||||
"appium:automationName": "Windows",
|
||||
},
|
||||
};
|
||||
|
||||
let driver;
|
||||
try {
|
||||
driver = await remote(opts);
|
||||
|
||||
// Github Actions Win ARM64 is stuck on a OOB screen that steals focus
|
||||
// These enter / escapes should dismiss it.
|
||||
const runner_arch = process.env.RUNNER_ARCH;
|
||||
if ( runner_arch === "ARM64" ) {
|
||||
console.log('Running on ARM64...');
|
||||
await sleep(3000); // Pause execution for 3 seconds
|
||||
await screenshot(driver, 'oob1.png');
|
||||
await driver.sendKeys([Key.Enter]);
|
||||
await sleep(3000); // Pause execution for 3 seconds
|
||||
await screenshot(driver, 'oob2.png');
|
||||
await driver.sendKeys([Key.Enter]);
|
||||
await sleep(3000); // Pause execution for 3 seconds
|
||||
await screenshot(driver, 'oob3.png');
|
||||
await driver.sendKeys([Key.Escape]);
|
||||
await screenshot(driver, 'oob6.png');
|
||||
} else {
|
||||
console.log('NOT running on ARM64');
|
||||
}
|
||||
|
||||
// Execute SSD again to open login dialog
|
||||
exec('"C:\\Program Files\\Certum\\SimplySign Desktop\\SimplySignDesktop.exe"', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`exec error: ${error}`);
|
||||
return;
|
||||
}
|
||||
});
|
||||
await sleep(3000);
|
||||
|
||||
// Login
|
||||
const windows = await driver.getWindowHandles();
|
||||
const login_window = windows[0]
|
||||
await driver.switchWindow(login_window);
|
||||
await screenshot(driver, 'login01.png');
|
||||
const id_value = 'jay0lee@gmail.com';
|
||||
const id_arr = [...id_value];
|
||||
await driver.sendKeys(id_arr);
|
||||
await screenshot(driver, 'login02.png');
|
||||
await driver.sendKeys([Key.Tab]);
|
||||
// We wait until the last possible second to generate
|
||||
// our TOTP to ensure it's still valid.
|
||||
const token_value = TOTP.generate(process.env.TOTP_SECRET, {algorithm: 'SHA-256'}).otp;
|
||||
const token_arr = [...token_value];
|
||||
await driver.sendKeys(token_arr);
|
||||
await screenshot(driver, 'login03.png');
|
||||
await driver.sendKeys([Key.Enter]);
|
||||
|
||||
// TODO: it's expected that on successful login the window
|
||||
// will close and these screenshots will error out. Figure
|
||||
// out how to handle that gracefully.
|
||||
await screenshot(driver, 'login04.png');
|
||||
await sleep(500);
|
||||
await screenshot(driver, 'login05.png');
|
||||
await sleep(500);
|
||||
await screenshot(driver, 'login06.png');
|
||||
await sleep(500);
|
||||
await screenshot(driver, 'login07.png');
|
||||
await sleep(500);
|
||||
await screenshot(driver, 'login08.png');
|
||||
await sleep(500);
|
||||
await screenshot(driver, 'login09.png');
|
||||
await sleep(500);
|
||||
await screenshot(driver, 'login10.png');
|
||||
await sleep(500);
|
||||
await screenshot(driver, 'login11.png');
|
||||
await sleep(500);
|
||||
await screenshot(driver, 'login12.png');
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error during Appium run:", error.name);
|
||||
}
|
||||
|
||||
// INTENTIONAL Keep driver open so tray icon for Certum doesn't close
|
||||
// finally {
|
||||
// if (driver) {
|
||||
// await driver.deleteSession(); // Close the Appium session
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
runSSD();
|
||||
@@ -7,6 +7,7 @@
|
||||
- [Display alerts](#display-alerts)
|
||||
- [Manage alert feedback](#manage-alert-feedback)
|
||||
- [Display alert feedback](#display-alert-feedback)
|
||||
- [Configuring settings](#configuring-settings)
|
||||
|
||||
## API documentation
|
||||
* [Alert Center API](https://developers.google.com/admin-sdk/alertcenter/reference/rest/)
|
||||
@@ -18,6 +19,7 @@
|
||||
## Definitions
|
||||
```
|
||||
<AlertID> ::= <String>
|
||||
<PubSubTopicName> ::= <String>
|
||||
<QueryAlert> ::= <String> See: https://developers.google.com/admin-sdk/alertcenter/guides/query-filters
|
||||
```
|
||||
## Introduction
|
||||
@@ -95,3 +97,15 @@ the quote character itself, the column delimiter (comma by default) and new-line
|
||||
When using the `formatjson` option, double quotes are used extensively in the data resulting in hard to read/process output.
|
||||
The `quotechar <Character>` option allows you to choose an alternate quote character, single quote for instance, that makes for readable/processable output.
|
||||
`quotechar` defaults to `gam.cfg/csv_output_quote_char`. When uploading CSV files to Google, double quote `"` should be used.
|
||||
|
||||
## Configuring settings
|
||||
|
||||
Alert Center can be configured to send notifications to a Google Cloud Pub/Sub topic, but it first requires configuration.
|
||||
* See https://developers.google.com/workspace/admin/alertcenter/guides/notifications for information.
|
||||
|
||||
Gam can be used to display or modify the settings:
|
||||
```
|
||||
gam show alertsettings
|
||||
gam update alertsettings <PubSubTopicName>
|
||||
gam clear alertsettings
|
||||
```
|
||||
@@ -436,6 +436,7 @@
|
||||
Must match this Python Regular Expression: [a-zA-Z0-9 '"!-]{4,30}
|
||||
<PropertyKey> ::= <String>
|
||||
<PropertyValue> ::= <String>
|
||||
<PubSubTopicName> ::= <String>
|
||||
<QueryAlert> ::= <String>
|
||||
See: https://developers.google.com/admin-sdk/alertcenter/guides/query-filters
|
||||
<QueryBrowser> ::= <String>
|
||||
|
||||
@@ -239,6 +239,10 @@ bookmarks.json
|
||||
|
||||
gam update chromepolicy chrome.users.ManagedBookmarksSetting json file bookmarks.json orgunit "/Students/
|
||||
```
|
||||
Allowlist the Google Translate extension for the Students OrgUnit
|
||||
```
|
||||
gam update chromepolicy chrome.users.apps.InstallType appInstallType ALLOWED app_id chrome:aapbdbdomjkkjkaonfhkkikfgjllcleb ou "/Students"
|
||||
```
|
||||
|
||||
## Delete Chrome policy
|
||||
You can delete a policy for all devices/users within an OU, users with a group or for a specific printer or application within an OU.
|
||||
@@ -9946,4 +9950,4 @@ chrome.users.ZstdContentEncodingEnabled: Zstd compression.
|
||||
false: Do not allow zstd-compressed web content.
|
||||
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
@@ -62,15 +62,6 @@ To use the `crostelemetry` commands you must authorize an additional scope:
|
||||
gam oauth create
|
||||
```
|
||||
|
||||
Many commands come in two forms:
|
||||
```
|
||||
gam <CrOSTypeEntity> <Command> ...
|
||||
gam <Command> cros <CrOSEntity> ...
|
||||
```
|
||||
The first form allows more powerful selection of devices with `<CrOSTypeEntity>`.
|
||||
|
||||
The second form is backwards compatible with Legacy GAM and selection with `<CrOSEntity>` is limited.
|
||||
|
||||
## Definitions
|
||||
* [`<CrOSTypeEntity>`](Collections-of-ChromeOS-Devices)
|
||||
|
||||
@@ -332,7 +323,6 @@ gam select default config update_cros_ou_with_id true save
|
||||
|
||||
```
|
||||
gam <CrOSTypeEntity> update <CrOSAttribute>+ [quickcrosmove [<Boolean>]] [nobatchupdate]
|
||||
gam update cros <CrOSEntity> <CrOSAttribute>+ [quickcrosmove [<Boolean>]] [nobatchupdate]
|
||||
```
|
||||
|
||||
Google has introduced a new, faster method for moving CrOS devices to a new OU. The `quickcrosmove` option controls which method Gam uses.
|
||||
@@ -419,8 +409,6 @@ gam update ou csvkmd cros.csv keyfield OU datafield deviceId add croscsvdata dev
|
||||
|
||||
gam <CrOSTypeEntity> update action <CrOSAction> [acknowledge_device_touch_requirement]
|
||||
[actionbatchsize <Integer>]
|
||||
gam update cros <CrOSEntity> action <CrOSAction> [acknowledge_device_touch_requirement]
|
||||
[actionbatchsize <Integer>]
|
||||
```
|
||||
As of GAM version `6.67.00`, the new API function `batchChangeStatus` replaces the old API function `action`; ChromeOS devices are now processed in batches.
|
||||
The batch size defaults to 10, the `actionbatchsize <Integer>` option can be used to set a batch size between 10 and 250.
|
||||
@@ -457,21 +445,18 @@ is configurable from 0 to some large number. If the status reaches `EXPIRED`, `C
|
||||
wipe_users|
|
||||
take_a_screenshot
|
||||
|
||||
gam cros <CrOSTypeEntity> issuecommand command <CrOSCommand> [times_to_check_status <Integer>] [doit]
|
||||
gam issuecommand cros <CrOSEntity> command <CrOSCommand> [times_to_check_status <Integer>] [doit]
|
||||
gam <CrOSTypeEntity> issuecommand command <CrOSCommand> [times_to_check_status <Integer>] [doit]
|
||||
```
|
||||
If the final status is not reached before GAM exits, you can issue the following commands to continue checking the status.
|
||||
```
|
||||
gam cros <CrOSTypeEntity> getcommand commandid <CommandID> [times_to_check_status <Integer>]
|
||||
gam getcommand cros <CrOSEntity> commandid <CommandID> [times_to_check_status <Integer>]
|
||||
gam <CrOSTypeEntity> getcommand commandid <CommandID> [times_to_check_status <Integer>]
|
||||
```
|
||||
|
||||
### Action Examples
|
||||
Remove user profile data from the device; the device will remain enrolled and connected.
|
||||
User data not synced to the Cloud including Downloads, Android app data and Crostini Linux VMs will be permanently lost.
|
||||
Commands with issuecommand directly after gam will work with Legacy GAM & GAM7, whereas commands where the issuecommand is after the cros <CrOSTypeEntity> will work only with GAM7.
|
||||
```
|
||||
gam issuecommand cros dd1d659a-0ea4-4e94-905e-4726c7a5f1e9 command wipe_users doit
|
||||
gam cros dd1d659a-0ea4-4e94-905e-4726c7a5f1e9 issuecommand command wipe_users doit
|
||||
```
|
||||
Remove profiles using the annotatedAssetID, which is a user editable field, in this example the device has an asset ID of CB1234.
|
||||
```
|
||||
@@ -483,14 +468,12 @@ gam cros_queries "asset_id:CB1234,asset_id:CB5678" issuecommand command wipe_use
|
||||
```
|
||||
Powerwash the device with serial number 143040348.
|
||||
```
|
||||
gam issuecommand cros query:id:143040348 command remote_powerwash times_to_check_status 10 doit
|
||||
gam cros_sn 143040348 issuecommand command remote_powerwash times_to_check_status 10 doit
|
||||
```
|
||||
|
||||
Powerwash all devices in the /StudentCarts OrgUnit. Devices will need to be manually reconnected to WiFi which may mean entering a PSK.
|
||||
Use `wipe_users` if that's going to create too much work for you.
|
||||
```
|
||||
gam issuecommand cros "query:orgunitpath:/StudentCarts" command remote_powerwash times_to_check_status 0 doit
|
||||
gam cros_ou /StudentCarts issuecommand command remote_powerwash times_to_check_status 0 doit
|
||||
```
|
||||
## ChromeOS device lists
|
||||
@@ -829,7 +812,6 @@ The `quotechar <Character>` option allows you to choose an alternate quote chara
|
||||
|
||||
```
|
||||
gam <CrOSTypeEntity> info downloadfile latest|<Time> [targetfolder <FilePath>]
|
||||
gam info cros <CrOSEntity> downloadfile latest|<Time> [targetfolder <FilePath>]
|
||||
```
|
||||
|
||||
Select the device file to download by its timestamp.
|
||||
|
||||
@@ -10,6 +10,68 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
|
||||
|
||||
See [Downloads-Installs-GAM7](https://github.com/GAM-team/GAM/wiki/Downloads-Installs) for Windows or other options, including manual installation
|
||||
|
||||
### 7.19.02
|
||||
|
||||
Update `gam info user <UserItem>` to eliminate 5 second delay when getting license info.
|
||||
|
||||
Additional information:
|
||||
* See: https://github.com/GAM-team/GAM/wiki/Licenses#info-user-performance
|
||||
|
||||
### 7.19.01
|
||||
|
||||
Updated `gam <UserTypeEntity> print|show signature` to handle the following error
|
||||
that occurs when an alias is specified.
|
||||
```
|
||||
ERROR: 404: notFound - Requested entity was not found.
|
||||
```
|
||||
|
||||
### 7.19.00
|
||||
|
||||
Eliminated `drive_v3_beta` and `meet_v2_beta` from `gam.cfg` as the API betas are no longer used.
|
||||
|
||||
Updated `Meet API` scopes so that GAM can read metadata about additional Meet spaces.
|
||||
```
|
||||
[*] 34) Meet API - Manage/Display Meeting Spaces
|
||||
[*] 35) Meet API - Read Meeting Spaces metadata
|
||||
```
|
||||
|
||||
### 7.18.07
|
||||
|
||||
Updated `gam <UserTypeEntity> print drivelastmodification` to put `addcsvdata` columns
|
||||
after `User,id,name` rather than after the last column.
|
||||
|
||||
### 7.18.06
|
||||
|
||||
Updated `gam <UserTypeEntity> delete|modify messages` to improve the handling
|
||||
of the following error.
|
||||
```
|
||||
quotaExceeded - User-rate limit exceeded
|
||||
```
|
||||
|
||||
### 7.18.05
|
||||
|
||||
Added support for Inbound SSO OIDC profiles.
|
||||
|
||||
Currently, if you enter `gam select <SectionName>` and nothing else on the command line,
|
||||
GAM performs no action. Now, it will be treated as if you entered:
|
||||
`gam select <SectionName> save`
|
||||
|
||||
Updated to Python 3.13.7.
|
||||
|
||||
### 7.18.04
|
||||
|
||||
Added commands to display/manage Alert Center Pub/Sub notifications.
|
||||
* See: https://github.com/GAM-team/GAM/wiki/Alert-Center#configuring-settings
|
||||
|
||||
### 7.18.03
|
||||
|
||||
Updated `gam oauth create` to give a warning if the number of selected scopes will
|
||||
probably cause Google to generate a "Something went wrong" error.
|
||||
|
||||
### 7.18.02
|
||||
|
||||
Upgraded to OpenSSL 3.5.2.
|
||||
|
||||
### 7.18.01
|
||||
|
||||
Added option `nosystemroles` to `gam print|show adminroles` that causes GAM
|
||||
|
||||
@@ -252,10 +252,10 @@ writes the credentials into the file oauth2.txt.
|
||||
admin@server:/Users/admin$ rm -f /Users/admin/GAMConfig/oauth2.txt
|
||||
admin@server:/Users/admin$ gam version
|
||||
WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found
|
||||
GAM 7.18.01 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.19.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.6 x86_64
|
||||
Python 3.13.7 64-bit final
|
||||
MacOS Sequoia 15.6.1 x86_64
|
||||
Path: /Users/admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
|
||||
@@ -990,9 +990,9 @@ writes the credentials into the file oauth2.txt.
|
||||
C:\>del C:\GAMConfig\oauth2.txt
|
||||
C:\>gam version
|
||||
WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found
|
||||
GAM 7.18.01 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM 7.19.02 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
Python 3.13.7 64-bit final
|
||||
Windows-10-10.0.17134 AMD64
|
||||
Path: C:\GAM7
|
||||
Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Inbound SSO
|
||||
- [Admin Console](#admin-console)
|
||||
- [Setup SSO](https://support.google.com/a/answer/12032922)
|
||||
- [Admin Console](https://admin.google.com/ac/security/sso)
|
||||
- [API documentation](#api-documentation)
|
||||
- [Definitions](#definitions)
|
||||
- [Setup SSO](#setupsso)
|
||||
- [Manage profiles](#manage-profiles)
|
||||
- [Display profiles](#display-profiles)
|
||||
- [Manage credentials](#manage-credentials)
|
||||
@@ -9,12 +11,10 @@
|
||||
- [Manage assignments](#manage-assignments)
|
||||
- [Display assignments](#display-assignments)
|
||||
|
||||
## Admin Console
|
||||
* https://admin.google.com/ac/security/sso
|
||||
|
||||
## API documentation
|
||||
* [Cloud Identity API - Inbound SAML SSO Profiles](https://cloud.google.com/identity/docs/reference/rest/v1beta1/inboundSamlSsoProfiles)
|
||||
* [Cloud Identity API - Inbound SAML SSO Profiles idp Credentials](https://cloud.google.com/identity/docs/reference/rest/v1beta1/inboundSamlSsoProfiles.idpCredentials)
|
||||
* [Cloud Identity API - Inbound OIDC SSO Profiles](https://cloud.google.com/identity/docs/reference/rest/v1beta1/inboundOidcSsoProfiles)
|
||||
* [Cloud Identity API - Inbound SSO Assignments](https://cloud.google.com/identity/docs/reference/rest/v1beta1/inboundSsoAssignments)
|
||||
|
||||
## Definitions
|
||||
@@ -41,46 +41,68 @@
|
||||
```
|
||||
## Manage profiles
|
||||
```
|
||||
gam create inboundssoprofile [name <SSOProfileDisplayName>]
|
||||
gam create inboundssoprofile [saml|oidc] [name <SSOProfileDisplayName>]
|
||||
[entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
[returnnameonly]
|
||||
gam update inboundssoprofile <SSOProfileItem>
|
||||
gam update inboundssoprofile [saml|oidc] <SSOProfileItem>
|
||||
[entityid <String>] [loginurl <URL>] [logouturl <URL>] [changepasswordurl <URL>]
|
||||
[returnnameonly]
|
||||
```
|
||||
Select type of profile:
|
||||
* `saml` - SAML profile; this is the default
|
||||
* `oidc` - OIDC profile
|
||||
|
||||
By default, all fields of the created|updated profile are displayed;
|
||||
use the `returnnameonly` option to have GAM display just the profile name of the created|updated profile.
|
||||
This will be useful in scripts that create|update a profile and then want to perform subsequent GAM commands that
|
||||
reference the profile.
|
||||
|
||||
If `returnnameonly is specified, `inProgress` is returned if the API does not return a complete result.
|
||||
If `returnnameonly` is specified, `inProgress` is returned if the API does not return a complete result.
|
||||
|
||||
```
|
||||
gam delete inboundssoprofile <SSOProfileItem>
|
||||
gam delete inboundssoprofile [saml|oidc] <SSOProfileItem>
|
||||
```
|
||||
Select type of profile:
|
||||
* `saml` - SAML profile; this is the default
|
||||
* `oidc` - OIDC profile
|
||||
|
||||
## Display profiles
|
||||
Display a specific profile.
|
||||
```
|
||||
gam info inboundssoprofile <SSOProfileItem>
|
||||
gam info inboundssoprofile [all|saml|oidc] <SSOProfileItem>
|
||||
[formatjson]
|
||||
```
|
||||
Select type of profile:
|
||||
* `all` - All profiles are displayed; this is the default
|
||||
* `saml` - SAML profile
|
||||
* `oidc` - OIDC profile
|
||||
|
||||
By default, Gam displays the information as an indented list of keys and values.
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
Display all profiles.
|
||||
Display profiles.
|
||||
```
|
||||
gam show inboundssoprofiles
|
||||
gam show inboundssoprofiles [all|saml|oidc]
|
||||
[formatjson]
|
||||
```
|
||||
Select profiles to display:
|
||||
* `all` - All profiles are displayed; this is the default
|
||||
* `saml` - Display SAML profiles
|
||||
* `oidc` - Display OIDC profiles
|
||||
|
||||
By default, Gam displays the information as an indented list of keys and values.
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
Display all profiles in a CSV file.
|
||||
Display profiles in a CSV file.
|
||||
```
|
||||
gam print inboundssoprofiles [todrive <ToDriveAttribute>*]
|
||||
gam print inboundssoprofiles [all|saml|oidc] [todrive <ToDriveAttribute>*]
|
||||
[[formatjson [quotechar <Character>]]
|
||||
```
|
||||
Select profiles to display:
|
||||
* `all` - All profiles are displayed; this is the default
|
||||
* `saml` - Display SAML profiles
|
||||
* `oidc` - Display OIDC profiles
|
||||
|
||||
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format,
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
@@ -130,10 +152,14 @@ The `quotechar <Character>` option allows you to choose an alternate quote chara
|
||||
|
||||
## Manage assignments
|
||||
```
|
||||
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 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 <SSOAssignmentName>
|
||||
[(group <GroupItem> rank <Number>)|(ou|org|orgunit <OrgUnitItem>)]
|
||||
[(mode sso_off)|(mode saml_sso profile <SSOProfileItem>)(mode domain_wide_saml_if_enabled)]
|
||||
[neverredirect]
|
||||
gam delete inboundssoassignment <SSOAssignmentSelector>
|
||||
```
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
- [License Products and SKUs](#license-products-and-skus)
|
||||
- [Definitions](#definitions)
|
||||
- [Notes](#Notes)
|
||||
- [Info User Performance](#info-user-performance)
|
||||
- [Display license counts](#display-license-counts)
|
||||
- [Display licenses](#display-licenses)
|
||||
- [Add licenses](#add-licenses)
|
||||
@@ -233,6 +234,27 @@ nv:<String>:<String>
|
||||
```
|
||||
The first `<String>` is a Product and the second `<String>` is a SKU.
|
||||
|
||||
## Info User Performance
|
||||
|
||||
In GAM versions prior 7.18.05, when you did `gam info user`, GAM would make one attempt to get the user's licenses.
|
||||
If something went wrong, you might not get the complete list.
|
||||
|
||||
The License Manager API doesn't have a call that returns the list of licenses that a user has; you have to ask:
|
||||
```
|
||||
Does user have license SKU 1?
|
||||
Does user have license SKU 2?
|
||||
Does user have license SKU 3?
|
||||
...
|
||||
Does user have license SKU 73?
|
||||
```
|
||||
If you do a couple of info user commands back to back, you start to run into quota issues.
|
||||
|
||||
You can help yourself in the following way: generate a list of all of the SKUs that exist in your workspace.
|
||||
|
||||
Then do (example, use actual list): gam config license_skus 1010020028,1010070001, ... save
|
||||
Now, rather that asking 73 questions per user, GAM will only ask about the license SKUs in the list.
|
||||
It is much less likely that quota issues will occur,
|
||||
|
||||
## Display license counts
|
||||
```
|
||||
gam show licenses
|
||||
|
||||
@@ -35,6 +35,9 @@ Select a section from gam.cfg and process a GAM command using values from that s
|
||||
- Print the variable values for the selected section
|
||||
- Values are determined in this order: Selected section, DEFAULT section, Program default
|
||||
|
||||
If you enter `gam select <SectionName>` and nothing else on the command line,
|
||||
it will be treated as if you entered: `gam select <SectionName> save`
|
||||
|
||||
### Display sections
|
||||
Display all of the sections in gam.cfg and mark the currently selected section with a *.
|
||||
```
|
||||
|
||||
@@ -392,7 +392,7 @@ Your command line will have: `embedimage file1.jpg image1 embedimage file2.jpg i
|
||||
|
||||
## Send an email to users
|
||||
```
|
||||
gam <UserTypeEntity> sendemail [from <EmailAddress>]
|
||||
gam <UserTypeEntity> sendemail from <EmailAddress>
|
||||
[replyto <EmailAddress>]
|
||||
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||
[subject <String>]
|
||||
@@ -406,8 +406,6 @@ gam <UserTypeEntity> sendemail [from <EmailAddress>]
|
||||
```
|
||||
Emails will be sent to the users in `<UserTypeEntity>`.
|
||||
|
||||
By default, emails will be sent from the admin user named in oauth2.txt, override this with the `from <EmailAddress>` option.
|
||||
|
||||
When using the Gmail API/SMTP, GAM gets no/little indication as to the status of the message delivery; the from user will get a non-delivery receipt if the message
|
||||
could not be sent to the specified recipients.
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@
|
||||
(foregroundcolor <ColorValue>)|
|
||||
(hidden <Boolean>)|
|
||||
(notification clear|(email <CalendarEmailNotificatonEventTypeList>))|
|
||||
(reminder clear|(email|popup <Number>)|(<Number> email|popup))|
|
||||
(reminder clear|(email|popup <Number>)|(<Number> email|popup))*|
|
||||
(selected <Boolean>)|
|
||||
(summary <String>)
|
||||
|
||||
|
||||
@@ -464,7 +464,7 @@ gam <UserItem> delete chatmember asadmin <ChatSpace>
|
||||
|
||||
Delete members from a chat space by specifying chatmember names, asadmin
|
||||
```
|
||||
gam <UserItem> remove chatmember members asadmin <ChatMemberList>
|
||||
gam <UserItem> remove chatmember asadmin members <ChatMemberList>
|
||||
```
|
||||
|
||||
### Update a members role in a user's chat space
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
- [Move with ownership change](#move-with-ownership-change)
|
||||
- [Complex moves](#complex-moves)
|
||||
- [Move content of a Shared Drive to another Shared Drive](#move-content-of-a-shared-drive-to-another-shared-drive)
|
||||
- [Move content of a Shared Drive to a My Drive](#move-content-of-a-shared-drive-to-a-my-drive)
|
||||
|
||||
## API documentation
|
||||
* [Drive API - Files](https://developers.google.com/drive/api/v3/reference/files)
|
||||
@@ -673,8 +674,10 @@ gam user user@domain.com move drivefile teamdriveid 0AC_1AB teamdriveparentid 0A
|
||||
```
|
||||
|
||||
If you want the source Shared Drive with ID 0AC_1AB to be contained in a top level folder of the target Shared Drive with ID 0AE_9ZX, omit the `mergewithparent` argument.
|
||||
The folder on the target Shared Drive will have the same name as the name of the source Shared Drive; use the `newfilename <DriveFileName>` to use a different name.
|
||||
```
|
||||
gam user user@domain.com move drivefile teamdriveid 0AC_1AB teamdriveparentid 0AE_9ZX
|
||||
gam user user@domain.com move drivefile teamdriveid 0AC_1AB teamdriveparentid 0AE_9ZX newfilename "Copy of source Shared Drive"
|
||||
```
|
||||
|
||||
### Inter-workspace moves
|
||||
@@ -692,3 +695,21 @@ User: user@domaina.com, Move 1 Drive File/Folder
|
||||
User: user@domaina.com, Drive Folder: Shared Drive A(<SharedDriveAID>), Retained
|
||||
```
|
||||
To get this to work, you must check `Allow people outside of Domain A to access files` on Shared Drive A in domaina.com
|
||||
|
||||
## Move content of a Shared Drive to a My Drive
|
||||
Suppose you have a Shared Drive with ID 0AC_1AB with multiple files and folders, and want to move all of its content to the root of a My Drive.
|
||||
|
||||
The following command will change the parents of the top level files and folders from 0AC_1AB to the root of the My Drive; the sub files and folders will move along with their top level folder.
|
||||
|
||||
* No permissions are processed.
|
||||
```
|
||||
gam user user@domain.com move drivefile teamdriveid 0AC_1AB parentid root mergewithparent
|
||||
```
|
||||
|
||||
If you want the contents of Shared Drive with ID 0AC_1AB to be contained in a top level folder of the My Drive, omit the `mergewithparent` argument.
|
||||
The folder on the My Drive will have the same name as the name of the Shared Drive; use the `newfilename <DriveFileName>` to use a different name.
|
||||
```
|
||||
gam user user@domain.com move drivefile teamdriveid 0AC_1AB parentid root
|
||||
gam user user@domain.com move drivefile teamdriveid 0AC_1AB parentid root newfilename "Copy of Shared Drive"
|
||||
```
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
- [Change shares to User1 to shares to User2](#change-shares-to-user1-to-shares-to-user2)
|
||||
- [Map All ACLs from an old domain to a new domain](#map-all-acls-from-an-old-domain-to-a-new-domain)
|
||||
- [Remove all ACLs for a specific user or group email address](#remove-all-ACLs-for-a-specific-user-or-group-email-address)
|
||||
- [Remove anyone-anyoneWithLink ACLs](#remove-anyone-anyonewithlink-acls)
|
||||
|
||||
## API documentation
|
||||
* [Drive API - Permissions](https://developers.google.com/drive/api/v3/reference/permissions)
|
||||
@@ -391,15 +392,15 @@ gam config csv_input_row_filter "permission.type:regex:domain" redirect stdout .
|
||||
### My Drives
|
||||
|
||||
Get My Drive ACLs sharing to that email address:
|
||||
* Replace <Type> with user or group
|
||||
* Replace email@domain.com with actual address
|
||||
* Replace `<Type>` with user or group
|
||||
* Replace `email@domain.com` with actual email address
|
||||
```
|
||||
gam config auto_batch_min 1 num_threads 20 redirect csv ./MyDriveShares.csv multiprocess redirect stderr - multiprocess all users print filelist fields id,name,mimetype,basicpermissions query "'email@domain.com' in readers or 'email@domain.com' in writers" pm notrole owner type <Type> emailaddress email@domain.com em pmfilter oneitemperrow
|
||||
```
|
||||
|
||||
Delete those My Drive ACLs.
|
||||
```
|
||||
gam config num_threads 20 redirect stdout ./DeleteMyDriveShares.txt multiprocess redirect stderr stdout csv MyDriveShares.csv gam user "~Owner" delete drivefleacl "~id" "id:~~permissions.id~~"
|
||||
gam config num_threads 20 redirect stdout ./DeleteMyDriveShares.txt multiprocess redirect stderr stdout csv MyDriveShares.csv gam user "~Owner" delete drivefleacl "~id" "id:~~permission.id~~"
|
||||
```
|
||||
|
||||
Add My Drive ACLs with a different email address and the same role.
|
||||
@@ -411,17 +412,18 @@ gam config num_threads 20 redirect stdout ./AddMyDriveShares.txt multiprocess re
|
||||
Get an organizer for each Shared Drive
|
||||
```
|
||||
gam redirect csv ./SharedDriveOrganizers.csv print shareddriveorganizers
|
||||
```
|
||||
|
||||
Get Shared Drive ACLs explicitly sharing to that email address:
|
||||
* Replace <Type> with user or group
|
||||
* Replace email@domain.com with actual address
|
||||
* Replace `<Type>` with user or group
|
||||
* Replace `email@domain.com` with actual email address
|
||||
```
|
||||
gam config num_threads 20 csv_input_row_filter "organizers:regex:^.+$" redirect csv ./SharedDriveShares.csv multiprocess redirect stderr - multiprocess csv SharedDriveOrganizers.csv gam user "~organizers" print filelist select shareddriveid "~id" fields id,name,mimetype,basicpermissions,driveid showdrivename query "'email@domain.com' in readers or 'email@domain.com' in writers" pm type <Type> emailaddress email@domain.com inherited false em pmfilter oneitemperrow
|
||||
```
|
||||
|
||||
Delete those Shared Drive ACLs.
|
||||
```
|
||||
gam config num_threads 20 redirect stdout ./DeleteSharedDriveShares.txt multiprocess redirect stderr stdout csv SharedDriveShares.csv gam user "~Owner" delete drivefleacl "~id" "id:~~permissions.id~~"
|
||||
gam config num_threads 20 redirect stdout ./DeleteSharedDriveShares.txt multiprocess redirect stderr stdout csv SharedDriveShares.csv gam user "~Owner" delete drivefleacl "~id" "id:~~permission.id~~"
|
||||
```
|
||||
|
||||
Add Shared Drive ACLs with a different email address and the same role.
|
||||
@@ -429,4 +431,39 @@ Add Shared Drive ACLs with a different email address and the same role.
|
||||
gam config num_threads 20 redirect stdout ./ReplaceSharedDriveShares.txt multiprocess redirect stderr stdout csv SharedDriveShares.csv gam user "~Owner" add drivefleacl "~id" "~permission.type" newemail@domain.rom role "~permission.role"
|
||||
```
|
||||
|
||||
## Remove anyone-anyoneWithLink ACLs
|
||||
|
||||
Here are the queries that will be used in these commands:
|
||||
* anyone - query "visibility='anyoneCanFind'"
|
||||
* anyoneWithLink - query "visibility='anyoneWithLink'"
|
||||
* both - query "(visibility='anyoneCanFind' or visibility='anyoneWithLink')"
|
||||
|
||||
### My Drives
|
||||
|
||||
Get My Drive anyone/anyoneWithLink ACLs
|
||||
```
|
||||
gam config auto_batch_min 1 num_threads 20 redirect csv ./MyDriveShares.csv multiprocess redirect stderr - multiprocess all users print filelist fields id,name,mimetype,basicpermissions <Query> pm type anyone em pmfilter oneitemperrow
|
||||
```
|
||||
|
||||
Delete those My Drive ACLs.
|
||||
```
|
||||
gam config num_threads 20 redirect stdout ./DeleteMyDriveShares.txt multiprocess redirect stderr stdout csv MyDriveShares.csv gam user "~Owner" delete drivefleacl "~id" "id:~~permission.id~~"
|
||||
```
|
||||
|
||||
### Shared Drives
|
||||
Get an organizer for each Shared Drive
|
||||
```
|
||||
gam redirect csv ./SharedDriveOrganizers.csv print shareddriveorganizers
|
||||
```
|
||||
|
||||
Get Shared Drive anyone/anyoneWithLink ACLs
|
||||
```
|
||||
gam config num_threads 20 csv_input_row_filter "organizers:regex:^.+$" redirect csv ./SharedDriveShares.csv multiprocess redirect stderr - multiprocess csv SharedDriveOrganizers.csv gam user "~organizers" print filelist select shareddriveid "~id" fields id,name,mimetype,basicpermissions,driveid showdrivename <Query> pm type anyone inherited false em pmfilter oneitemperrow
|
||||
```
|
||||
|
||||
Delete those Shared Drive ACLs.
|
||||
```
|
||||
gam config num_threads 20 redirect stdout ./DeleteSharedDriveShares.txt multiprocess redirect stderr stdout csv SharedDriveShares.csv gam user "~Owner" delete drivefleacl "~id" "id:~~permission.id~~"
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -29,9 +29,10 @@ To use these commands you must add the 'Meet API' to your project and update you
|
||||
gam update project
|
||||
gam user user@domain.com update serviceaccount
|
||||
...
|
||||
[*] 36) Meet API (supports readonly)
|
||||
|
||||
[*] 34) Meet API - Manage/Display Meeting Spaces
|
||||
[*] 35) Meet API - Read Meeting Spaces metadata
|
||||
```
|
||||
|
||||
## Definitions
|
||||
* [`<UserTypeEntity>`](Collections-of-Users)
|
||||
```
|
||||
|
||||
@@ -16,7 +16,10 @@ Delegated admin service accounts (DASA) are regular [GCP service accounts](https
|
||||
|
||||
## Disadvantages
|
||||
* DASA accounts can only be delegated admins. [If a task requires super admin rights to perform](https://support.google.com/a/answer/2405986#:~:text=Only%20super%20administrators%20can...), DASA accounts won’t be able to do it.
|
||||
Not all Google Admin APIs work with DASA right now. For example, Google Vault API calls will fail with a DASA account; Classroom API calls do not return data.
|
||||
Not all Google Admin APIs work with DASA right no:
|
||||
* Google Vault API calls will fail with a DASA account
|
||||
* Classroom API calls do not return data
|
||||
* Cloud Identity Policies are not available
|
||||
* DASA is a delegated admin and can make Workspace / Cloud Identity admin API calls, it does not replace domain-wide delegation (DwD) when using GAM7 commands that interact with Gmail, Drive and Calendar user data.
|
||||
* GAM7 support for DASA is still experimental and some things may fail. Please report your findings to the [GAM group](https://groups.google.com/g/google-apps-manager).
|
||||
|
||||
|
||||
@@ -39,6 +39,16 @@
|
||||
[Collections of Items](Collections-of-Items)
|
||||
```
|
||||
<AttendeeStatus> ::= accepted|declined|needsaction|tentative
|
||||
<Date> ::=
|
||||
<Year>-<Month>-<Day> |
|
||||
(+|-)<Number>(d|w|y) |
|
||||
never|
|
||||
today
|
||||
<Time> ::=
|
||||
<Year>-<Month>-<Day>(<Space>|T)<Hour>:<Minute>:<Second>[.<MilliSeconds>](Z|(+|-(<Hour>:<Minute>))) |
|
||||
(+|-)<Number>(m|h|d|w|y) |
|
||||
never|
|
||||
now|today
|
||||
<EmailItem> ::= <EmailAddress>|<UniqueID>|<String>
|
||||
<EmailItemList> ::= "<EmailItem>(,<EmailItem>)*"
|
||||
<EmailAddressList> ::= "<EmailAddess>(,<EmailAddress>)*"
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
Print the current version of Gam with details
|
||||
```
|
||||
gam version
|
||||
GAM 7.18.01 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.19.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.6 x86_64
|
||||
Python 3.13.7 64-bit final
|
||||
MacOS Sequoia 15.6.1 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Time: 2023-06-02T21:10:00-07:00
|
||||
@@ -15,10 +15,10 @@ Time: 2023-06-02T21:10:00-07:00
|
||||
Print the current version of Gam with details and time offset information
|
||||
```
|
||||
gam version timeoffset
|
||||
GAM 7.18.01 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.19.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.6 x86_64
|
||||
Python 3.13.7 64-bit final
|
||||
MacOS Sequoia 15.6.1 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Your system time differs from www.googleapis.com by less than 1 second
|
||||
@@ -27,15 +27,15 @@ Your system time differs from www.googleapis.com by less than 1 second
|
||||
Print the current version of Gam with extended details and SSL information
|
||||
```
|
||||
gam version extended
|
||||
GAM 7.18.01 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.19.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.6 x86_64
|
||||
Python 3.13.7 64-bit final
|
||||
MacOS Sequoia 15.6.1 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Time: 2023-06-02T21:10:00-07:00
|
||||
Your system time differs from admin.googleapis.com by less than 1 second
|
||||
OpenSSL 3.4.0 22 Oct Sep 2024
|
||||
OpenSSL 3.5.2 5 ASug 2025
|
||||
cryptography 43.0.3
|
||||
filelock 3.16.1
|
||||
google-api-python-client 2.149.0
|
||||
@@ -64,7 +64,7 @@ MacOS High Sierra 10.13.6 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Version Check:
|
||||
Current: 5.35.08
|
||||
Latest: 7.18.01
|
||||
Latest: 7.19.02
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -72,7 +72,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
7.18.01
|
||||
7.19.02
|
||||
```
|
||||
In Linux/MacOS you can do:
|
||||
```
|
||||
@@ -82,10 +82,10 @@ echo $VER
|
||||
Print the current version of Gam and address of this Wiki
|
||||
```
|
||||
gam help
|
||||
GAM 7.18.01 - https://github.com/GAM-team/GAM
|
||||
GAM 7.19.02 - https://github.com/GAM-team/GAM
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.6 x86_64
|
||||
Python 3.13.7 64-bit final
|
||||
MacOS Sequoia 15.6.1 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Time: 2023-06-02T21:10:00-07:00
|
||||
|
||||
Reference in New Issue
Block a user