mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-05 06:41:38 +00:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b158496bea | ||
|
|
a79b23e090 | ||
|
|
bdb56240f0 | ||
|
|
6dddf3eb30 | ||
|
|
7bd8569151 | ||
|
|
b03c9f1e35 | ||
|
|
057b5ff760 | ||
|
|
ba512b4159 | ||
|
|
a298aea2fe | ||
|
|
f433463074 | ||
|
|
afae08d6fe | ||
|
|
7cf2a08aff | ||
|
|
7df6781985 | ||
|
|
ae0f5e62e3 | ||
|
|
14c8356c6b | ||
|
|
45ffd4a793 | ||
|
|
eb8d39025e | ||
|
|
1f739e1c63 | ||
|
|
82111236fb | ||
|
|
813a94f8d6 | ||
|
|
e83b75e2c3 | ||
|
|
ce1e880ed0 | ||
|
|
427672065e | ||
|
|
055c5d5e54 | ||
|
|
4de7794e04 | ||
|
|
79686fd8ce | ||
|
|
cc5df0198b | ||
|
|
abc6e55ba7 | ||
|
|
0c8afb7fd6 | ||
|
|
c0c2cca44e | ||
|
|
faa645cb97 | ||
|
|
725c19aafc | ||
|
|
cc3b4c974d | ||
|
|
6ce64fad72 | ||
|
|
c1af67d4a3 | ||
|
|
802cb15007 | ||
|
|
b34bf3e56a | ||
|
|
bf37700088 | ||
|
|
4a43ddfc25 | ||
|
|
650a1f5154 | ||
|
|
5eda7e30b0 | ||
|
|
8a26f547e5 | ||
|
|
343088913f | ||
|
|
5a0272fd5b | ||
|
|
dc93503625 | ||
|
|
6ea6c0889b | ||
|
|
99ab72df3f | ||
|
|
99bda1385e | ||
|
|
7ce3b4a8c0 | ||
|
|
495722d0d6 | ||
|
|
aca31be5d7 | ||
|
|
b9b7ae8d99 | ||
|
|
0d46c1d13a | ||
|
|
6b63ecdc19 | ||
|
|
f9ca0323a1 | ||
|
|
c50aa4d2e8 | ||
|
|
a72ded9079 | ||
|
|
cbabbee075 | ||
|
|
f55a344b7a | ||
|
|
d84f8418ff | ||
|
|
30c5e92de6 | ||
|
|
5f618a7f65 | ||
|
|
3e833419db | ||
|
|
0d94bae0b5 | ||
|
|
f5dec96ffb | ||
|
|
e91d12caaf | ||
|
|
fd5a1faa58 | ||
|
|
90a9212793 | ||
|
|
7e582ac1fc | ||
|
|
65a740569c | ||
|
|
a47ef0e1f5 |
14
.github/actions/linux-before-install.sh
vendored
14
.github/actions/linux-before-install.sh
vendored
@@ -95,18 +95,8 @@ else
|
||||
pip=~/python/bin/pip3
|
||||
|
||||
if ([ "${ImageOS}" == "ubuntu20" ]) && [ "${HOSTTYPE}" == "x86_64" ]; then
|
||||
echo "Installing deps for StaticX..."
|
||||
if [ ! -d patchelf-$PATCHELF_VERSION ]; then
|
||||
echo "Downloading PatchELF $PATCHELF_VERSION"
|
||||
wget https://github.com/NixOS/patchelf/archive/$PATCHELF_VERSION.tar.gz
|
||||
tar xf $PATCHELF_VERSION.tar.gz
|
||||
cd patchelf-$PATCHELF_VERSION/
|
||||
./bootstrap.sh
|
||||
./configure
|
||||
make
|
||||
sudo make install
|
||||
fi
|
||||
$pip install staticx
|
||||
"${python}" -m pip install --upgrade patchelf-wrapper
|
||||
"${python}" -m pip install --upgrade staticx
|
||||
fi
|
||||
|
||||
cd $whereibelong
|
||||
|
||||
2
.github/actions/macos-install.sh
vendored
2
.github/actions/macos-install.sh
vendored
@@ -7,7 +7,7 @@ export distpath="dist/"
|
||||
export gampath="${distpath}gam"
|
||||
rm -rf $gampath
|
||||
export specfile="gam.spec"
|
||||
$python -OO -m PyInstaller --clean --noupx --strip --distpath "${gampath}" --target-architecture $PLATFORM "${specfile}"
|
||||
$python -OO -m PyInstaller --distpath "${gampath}" "${specfile}"
|
||||
export gam="${gampath}/gam"
|
||||
$gam version extended
|
||||
export GAMVERSION=`$gam version simple`
|
||||
|
||||
136
.github/workflows/build.yml
vendored
136
.github/workflows/build.yml
vendored
@@ -12,13 +12,13 @@ defaults:
|
||||
working-directory: src
|
||||
|
||||
env:
|
||||
BUILD_PYTHON_VERSION: "3.10.0"
|
||||
MIN_PYTHON_VERSION: "3.10.0"
|
||||
BUILD_OPENSSL_VERSION: "3.0.0"
|
||||
BUILD_PYTHON_VERSION: "3.10.1"
|
||||
MIN_PYTHON_VERSION: "3.10.1"
|
||||
BUILD_OPENSSL_VERSION: "3.0.1"
|
||||
MIN_OPENSSL_VERSION: "1.1.1l"
|
||||
PATCHELF_VERSION: "0.13"
|
||||
# PYINSTALLER_VERSION can be full commit hash or version like v4.20
|
||||
PYINSTALLER_VERSION: "6eae2c7cf93a968ddc054339e0cb3063f90d0e64"
|
||||
#PYINSTALLER_VERSION: "86eeca8b4ba8012ab2df19ca206cafbe263b6a81"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -41,13 +41,13 @@ jobs:
|
||||
goal: "build"
|
||||
gamos: "macos"
|
||||
platform: "universal2"
|
||||
- os: windows-2019
|
||||
- os: windows-2022
|
||||
jid: 4
|
||||
goal: "build"
|
||||
gamos: "windows"
|
||||
pyarch: "x64"
|
||||
platform: "x86_64"
|
||||
- os: windows-2019
|
||||
- os: windows-2022
|
||||
jid: 5
|
||||
goal: "build"
|
||||
gamos: "windows"
|
||||
@@ -55,28 +55,32 @@ jobs:
|
||||
pyarch: "x86"
|
||||
- os: ubuntu-20.04
|
||||
goal: "test"
|
||||
python: "3.6"
|
||||
python: "3.7"
|
||||
jid: 6
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
- os: ubuntu-20.04
|
||||
goal: "test"
|
||||
python: "3.7"
|
||||
jid: 7
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
- os: ubuntu-20.04
|
||||
goal: "test"
|
||||
python: "3.8"
|
||||
jid: 8
|
||||
jid: 7
|
||||
gamos: "linux"
|
||||
platform: "x86_64"
|
||||
- os: ubuntu-20.04
|
||||
goal: test
|
||||
python: "3.9"
|
||||
jid: 9
|
||||
jid: 8
|
||||
gamos: linux
|
||||
platform: x86_64
|
||||
- os: [self-hosted, linux, arm64]
|
||||
jid: 9
|
||||
goal: "self-build"
|
||||
platform: "aarch64"
|
||||
gamos: linux
|
||||
- os: [self-hosted, linux, arm]
|
||||
jid: 10
|
||||
goal: "self-build"
|
||||
platform: "armv7l"
|
||||
gamos: linux
|
||||
|
||||
steps:
|
||||
|
||||
@@ -92,7 +96,7 @@ jobs:
|
||||
path: |
|
||||
~/python
|
||||
~/ssl
|
||||
key: ${{ matrix.os }}-${{ matrix.jid }}-20211122
|
||||
key: ${{ matrix.os }}-${{ matrix.jid }}-20211228
|
||||
|
||||
- name: Set env variables
|
||||
env:
|
||||
@@ -115,7 +119,7 @@ jobs:
|
||||
architecture: ${{ matrix.pyarch }}
|
||||
|
||||
- name: Install Python on Windows
|
||||
if: matrix.os == 'windows-2019'
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
if ( ${Env:PLATFORM} -eq "x86_64" )
|
||||
{
|
||||
@@ -134,8 +138,15 @@ jobs:
|
||||
Start-Process -wait -FilePath $python_file -ArgumentList "/quiet","InstallAllUsers=0","TargetDir=c:\\python","AssociateFiles=1","PrependPath=1"
|
||||
shell: pwsh
|
||||
|
||||
- name: Set env variables for pre-compiled Python
|
||||
- name: Install packages for test
|
||||
if: matrix.goal == 'test'
|
||||
run: |
|
||||
echo "RUNNING: apt update..."
|
||||
sudo apt-get -qq --yes update > /dev/null
|
||||
sudo apt-get -qq --yes install swig libpcsclite-dev
|
||||
|
||||
- name: Set env variables for pre-compiled Python
|
||||
if: matrix.goal != 'build'
|
||||
run: |
|
||||
export python=$(which python3)
|
||||
export pip=$(which pip3)
|
||||
@@ -146,51 +157,73 @@ jobs:
|
||||
echo "pip=${pip}" >> $GITHUB_ENV
|
||||
echo "gam=${gam}" >> $GITHUB_ENV
|
||||
echo "gampath=${gampath}" >> $GITHUB_ENV
|
||||
echo "RUNNING: apt update..."
|
||||
sudo apt-get -qq --yes update > /dev/null
|
||||
sudo apt-get -qq --yes install swig libpcsclite-dev
|
||||
$pip install --upgrade pip
|
||||
"${python}" -V
|
||||
"${pip}" -V
|
||||
|
||||
- name: Build and install Python, OpenSSL and PyInstaller
|
||||
if: matrix.goal != 'test' && steps.cache-primes.outputs.cache-hit != 'true'
|
||||
- name: Build and install Python and OpenSSL
|
||||
if: matrix.goal == 'build' && steps.cache-primes.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
set +e
|
||||
source ../.github/actions/${GAMOS}-before-install.sh
|
||||
echo "PATH=$PATH" >> $GITHUB_ENV # keep gnutools for MacOS
|
||||
echo "python=$python" >> $GITHUB_ENV
|
||||
echo "pip=$pip" >> $GITHUB_ENV
|
||||
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> $GITHUB_ENV
|
||||
echo -e "Python: $python\nPip: $pip\nLD_LIB...: $LD_LIBRARY_PATH"
|
||||
$pip install --upgrade pip
|
||||
$pip install wheel
|
||||
export url="https://codeload.github.com/pyinstaller/pyinstaller/tar.gz/${PYINSTALLER_VERSION}"
|
||||
echo "Downloading ${url}"
|
||||
curl -o pyinstaller.tar.gz --compressed "${url}"
|
||||
tar xf pyinstaller.tar.gz
|
||||
cd "pyinstaller-${PYINSTALLER_VERSION}/"
|
||||
if [ $GAMOS == "windows" ]; then
|
||||
# remove pre-compiled bootloaders so we fail if bootloader compile fails
|
||||
rm -rf PyInstaller/bootloader/*bit
|
||||
cd bootloader
|
||||
if [ "${PLATFORM}" == "x86" ]; then
|
||||
TARGETARCH="--target-arch=32bit"
|
||||
else
|
||||
TARGETARCH=""
|
||||
fi
|
||||
$python ./waf all $TARGETARCH
|
||||
cd ..
|
||||
if [ $GAMOS == "macos" ]; then
|
||||
export pipoptions='--no-binary ":all:"'
|
||||
echo "PATH=$PATH" >> $GITHUB_ENV # keep gnutools for MacOS
|
||||
export MACOSX_DEPLOYMENT_TARGET="10.9"
|
||||
export CFLAGS="-arch arm64 -arch x86_64"
|
||||
fi
|
||||
$pip install .
|
||||
#$python setup.py install
|
||||
#$pip install pyinstaller
|
||||
$pip install --upgrade pip $pipoptions
|
||||
$pip install wheel $pipoptions
|
||||
|
||||
- name: Install pip requirements
|
||||
if: matrix.os != 'self-hosted'
|
||||
- name: Set Windows Powershell env variables
|
||||
if: matrix.goal != 'test' && matrix.os == 'windows-2022' && matrix.platform == 'x86_64'
|
||||
shell: powershell
|
||||
run: |
|
||||
choco install nasm --no-progress
|
||||
$env:PATH="$ENV:PATH;c:\Program Files\NASM\"
|
||||
cmd /c 'call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && set MAKE=nmake && set > %temp%\vcvars.txt'
|
||||
Get-Content "$env:temp\vcvars.txt" | Foreach-Object {
|
||||
if ($_ -match "^(.*?)=(.*)$") {
|
||||
if ($matches[1] -eq "PATH" -or $matches[1] -eq "PLATFORM") {
|
||||
continue
|
||||
}
|
||||
Set-Content "env:\$($matches[1])" $matches[2]
|
||||
Add-Content -Path $env:GITHUB_ENV -Value "$($matches[1])=$($matches[2])"
|
||||
}
|
||||
}
|
||||
|
||||
- name: Install PyInstaller
|
||||
if: matrix.goal != 'test'
|
||||
run: |
|
||||
set +e
|
||||
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U --force-reinstall
|
||||
git clone https://github.com/pyinstaller/pyinstaller.git
|
||||
cd pyinstaller
|
||||
# remove pre-compiled bootloaders so we fail if bootloader compile fails
|
||||
rm -rf PyInstaller/bootloader/*-*/*
|
||||
cd bootloader
|
||||
export DefaultWindowsSDKVersion="10.0.20348.0"
|
||||
if [ "${PLATFORM}" == "x86" ]; then
|
||||
TARGETARCH="--target-arch=32bit"
|
||||
fi
|
||||
$python ./waf all $TARGETARCH
|
||||
cat build/config.log
|
||||
cd ..
|
||||
$pip install .
|
||||
|
||||
$pip install --upgrade -r requirements.txt
|
||||
- name: Install pip requirements
|
||||
run: |
|
||||
set +e
|
||||
if [ $GAMOS == "macos" ]; then
|
||||
#export pipoptions='--no-binary ":all:"'
|
||||
export MACOSX_DEPLOYMENT_TARGET="10.9"
|
||||
export CFLAGS="-arch arm64 -arch x86_64"
|
||||
fi
|
||||
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U --force-reinstall $pipoptions
|
||||
$pip install --upgrade -r requirements.txt $pipoptions
|
||||
|
||||
- name: Build GAM with PyInstaller
|
||||
if: matrix.goal != 'test'
|
||||
@@ -228,7 +261,7 @@ jobs:
|
||||
|
||||
- name: Live API tests push only
|
||||
if: github.event_name == 'push' || github.event_name == 'schedule'
|
||||
env: # Or as an environment variable
|
||||
env:
|
||||
PASSCODE: ${{ secrets.PASSCODE }}
|
||||
run: |
|
||||
source ../.github/actions/decrypt.sh ../.github/actions/creds.tar.gpg creds.tar
|
||||
@@ -253,7 +286,7 @@ jobs:
|
||||
for i in {01..10}; do
|
||||
echo "${newbase}-bulkuser-$i" >> sample.csv;
|
||||
done
|
||||
$gam create user $newuser firstname GHA lastname $JID password random recoveryphone 12125121110 recoveryemail jay0lee@gmail.com gha.jid $JID
|
||||
$gam create user $newuser firstname GHA lastname $JID password random recoveryphone 12125121110 recoveryemail jay0lee@gmail.com gha.jid $JID languages en+,en-GB-
|
||||
$gam user $gam_user sendemail recipient $newuser subject "test message $newbase" message "GHA test message"
|
||||
$gam user $gam_user sendemail recipient exchange@pdl.jaylee.us subject "test ${tstamp}" message "test message"
|
||||
$gam create group $newgroup name "GHA $JID group" description "This is a description" isarchived true
|
||||
@@ -334,6 +367,7 @@ jobs:
|
||||
export sn="$JID$JID$JID$JID-$(openssl rand -base64 32 | sed 's/[^a-zA-Z0-9]//g')"
|
||||
$gam create device serialnumber $sn devicetype android
|
||||
$gam print cros allfields orderby serialnumber
|
||||
#$gam show crostelemetry storagepercentonly
|
||||
$gam report usageparameters customer
|
||||
$gam report usage customer parameters gmail:num_emails_sent,accounts:num_1day_logins
|
||||
$gam report customer todrive
|
||||
|
||||
10
README.md
10
README.md
@@ -1,4 +1,4 @@
|
||||
GAM is a command line tool for Google Workspace (fka G Suite) Administrators to manage domain and user settings quickly and easily.
|
||||
GAM is a command line tool for Google Workspace admins to manage domain and user settings quickly and easily.
|
||||
|
||||

|
||||
|
||||
@@ -14,14 +14,6 @@ bash <(curl -s -S -L https://git.io/install-gam)
|
||||
|
||||
this will download GAM, install it and start setup.
|
||||
|
||||
To install with `pip`, run
|
||||
|
||||
```sh
|
||||
pip install git+https://github.com/jay0lee/GAM.git#subdirectory=src
|
||||
```
|
||||
|
||||
This will only download and install GAM. To start setup, simply invoke the `gam` CLI.
|
||||
|
||||
## Windows
|
||||
|
||||
Download the MSI Installer from the [GitHub Releases] page. Install the MSI and you'll be prompted to setup GAM.
|
||||
|
||||
@@ -207,8 +207,9 @@ If an item contains spaces, it should be surrounded by ".
|
||||
<Namespace> ::= <String>
|
||||
<NotificationID> ::= <String>
|
||||
<NumberOfSeats> ::= <Number>
|
||||
<OrgUnitID> ::= <String>
|
||||
<OrgUnitID> ::= id:<String>
|
||||
<OrgUnitPath> ::= /|(/<String)+
|
||||
<OrgUnitItem> ::= <OrgUnitID>|<OrgUnitPath>
|
||||
<ParameterKey> ::= <String>
|
||||
<ParameterValue> ::= <String>
|
||||
<Password> ::= <String>
|
||||
@@ -326,6 +327,7 @@ If an item contains spaces, it should be surrounded by ".
|
||||
description|
|
||||
editable|
|
||||
explicitlytrashed|
|
||||
driveid|
|
||||
fileextension|
|
||||
filesize|
|
||||
foldercolorrgb|
|
||||
@@ -594,7 +596,7 @@ Items, separated by spaces, with spaces, commas or single quotes in the items th
|
||||
<GroupRoleList> ::= "<GroupRole>(,<GroupRole>)*"
|
||||
<GuardianStateList> ::= "<GuardianState>(,<GuardianState>)*"
|
||||
<LabelNameList> ::= "<LabelName>(,<LabelName)*"
|
||||
<LanguageList> ::= "<Language>(,<Language)*"
|
||||
<LanguageList> ::= "<Language>[+|-](,<Language>[+|-])*"
|
||||
<MatterItemList> ::= "<MatterItem>(,<MatterItem>)*"
|
||||
<MembersFieldNameList> ::= "<MembersFieldName>(,<MembersFieldName>)*"
|
||||
<MobileList> ::= "<MobileId>(,<MobileId>)*"
|
||||
@@ -1017,7 +1019,8 @@ gam info customer
|
||||
|
||||
gam create datatransfer|transfer <OldOwnerID> <DataTransferServiceList> <NewOwnerID> (<ParameterKey> <ParameterValue>)*
|
||||
gam info datatransfer|transfer <TransferID>
|
||||
gam print datatransfers|transfers [todrive] [olduser|oldowner <UserItem>] [newuser|newowner <UserItem>] [status <String>]
|
||||
gam print datatransfers|transfers [todrive] [olduser|oldowner <UserItem>] [newuser|newowner <UserItem>]
|
||||
[status completed|failed|inprogress]
|
||||
|
||||
gam print transferapps
|
||||
|
||||
@@ -1469,7 +1472,11 @@ gam create user <EmailAddress> <UserAttribute>* [verifynotinvitable]
|
||||
gam update user <UserItem> <UserAttribute>* [clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>] [verifynotinvitable]
|
||||
gam delete user <UserItem>
|
||||
gam undelete user <UserItem> [org|ou <OrgUnitPath>]
|
||||
gam info user [<UserItem>] [noaliases] [nogroups] [nolicenses|nolicences] [noschemas] [schemas|custom <SchemaNameList>] [userview] [skus|sku <SKUIDList>] [grouptree]
|
||||
gam info user [<UserItem>]
|
||||
[quick] [noaliases] [nogroups] [nolicenses|nolicences] [noschemas]
|
||||
[skus|sku <SKUIDList>] [grouptree]
|
||||
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
||||
[schemas|custom all|<SchemaNameList>]
|
||||
|
||||
Print fields for selected users; use domain, query/queries and deleted_only to select users to print;
|
||||
if none of these options are specified, all users are printed.
|
||||
@@ -1477,10 +1484,12 @@ The first column will always be primaryEmail; the remaining field names will be
|
||||
otherwise, the remaining field names will appear in the order specified.
|
||||
|
||||
gam print users [todrive]
|
||||
([domain <DomainName>] [(query <QueryUser>)|(queries <QueryUserList>)] [deleted_only|only_deleted])
|
||||
([domain <DomainName>] [(query <QueryUser>)|(queries <QueryUserList>)]
|
||||
[limittoou <OrgUnitPath>] [deleted_only|only_deleted])
|
||||
[groups] [license|licenses|licence|licences] [emailpart|emailparts|username]
|
||||
[orderby <UserOrderByFieldName> [ascending|descending]] [userview]
|
||||
[allfields|basic|full | ((<UserFieldName>* | fields <UserFieldNameList>) [schemas|custom all|<SchemaNameList>])]
|
||||
[orderby <UserOrderByFieldName> [ascending|descending]]
|
||||
[userview] [allfields|basic|full | (<UserFieldName>* | fields <UserFieldNameList>)]
|
||||
[schemas|custom all|<SchemaNameList>])]
|
||||
[delimiter <Character>] [sortheaders]
|
||||
|
||||
gam create verify|verification <DomainName>
|
||||
|
||||
@@ -33,6 +33,11 @@ for d in a.datas:
|
||||
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
if sys.platform == "darwin":
|
||||
target_arch="universal2"
|
||||
else:
|
||||
target_arch=None
|
||||
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
@@ -42,4 +47,5 @@ exe = EXE(pyz,
|
||||
debug=False,
|
||||
strip=None,
|
||||
upx=False,
|
||||
target_arch=target_arch,
|
||||
console=True)
|
||||
|
||||
@@ -3273,7 +3273,7 @@ def printDriveFileList(users):
|
||||
'orderby', ', '.join(sorted(DRIVEFILE_ORDERBY_CHOICES_MAP)),
|
||||
fieldName)
|
||||
elif myarg == 'query':
|
||||
query += f' and {sys.argv[i+1]}'
|
||||
query += f' and ({sys.argv[i+1]})'
|
||||
i += 2
|
||||
elif myarg == 'fullquery':
|
||||
query = sys.argv[i + 1]
|
||||
@@ -6693,13 +6693,27 @@ def getUserAttributes(i, cd, updateCmd):
|
||||
i += 1
|
||||
continue
|
||||
for language in sys.argv[i].replace(',', ' ').split():
|
||||
if language.lower() in LANGUAGE_CODES_MAP:
|
||||
appendItemToBodyList(
|
||||
body, 'languages',
|
||||
{'languageCode': LANGUAGE_CODES_MAP[language.lower()]})
|
||||
lang_item = {}
|
||||
if language[-1] == '+':
|
||||
suffix = '+'
|
||||
language = language[:-1]
|
||||
lang_item['preference'] = 'preferred'
|
||||
elif language[-1] == '-':
|
||||
suffix = '-'
|
||||
language = language[:-1]
|
||||
lang_item['preference'] = 'not_preferred'
|
||||
else:
|
||||
appendItemToBodyList(body, 'languages',
|
||||
{'customLanguage': language})
|
||||
suffix = ''
|
||||
if language.lower() in LANGUAGE_CODES_MAP:
|
||||
lang_item['languageCode'] = LANGUAGE_CODES_MAP[language.lower()]
|
||||
else:
|
||||
if suffix:
|
||||
controlflow.system_error_exit(
|
||||
2,
|
||||
f'suffix {suffix} not allowed with customLanguage {language}'
|
||||
)
|
||||
lang_item['customLanguage'] = language
|
||||
appendItemToBodyList(body, 'languages', lang_item)
|
||||
i += 1
|
||||
elif myarg == 'gender':
|
||||
i += 1
|
||||
@@ -8770,6 +8784,20 @@ def _get_admin_email():
|
||||
)
|
||||
return _getValueFromOAuth('email')
|
||||
|
||||
def _formatLanguagesList(propertyValue, delimiter):
|
||||
languages = []
|
||||
for language in propertyValue:
|
||||
if 'languageCode' in language:
|
||||
lang = language['languageCode']
|
||||
if language.get('preference') == 'preferred':
|
||||
lang += '+'
|
||||
elif language.get('preference') == 'not_preferred':
|
||||
lang += '-'
|
||||
else:
|
||||
lang = language.get('customLanguage')
|
||||
languages.append(lang)
|
||||
return delimiter.join(languages)
|
||||
|
||||
def doGetUserInfo(user_email=None):
|
||||
|
||||
def user_lic_result(request_id, response, exception):
|
||||
@@ -8784,6 +8812,7 @@ def doGetUserInfo(user_email=None):
|
||||
i = 4
|
||||
else:
|
||||
user_email = _get_admin_email()
|
||||
fieldsList = []
|
||||
getSchemas = True
|
||||
getAliases = True
|
||||
getGroups = True
|
||||
@@ -8814,10 +8843,35 @@ def doGetUserInfo(user_email=None):
|
||||
getSchemas = False
|
||||
projection = 'basic'
|
||||
i += 1
|
||||
elif myarg == 'quick':
|
||||
getAliases = getCIGroups = getGroups = getLicenses = getSchemas = False
|
||||
i += 1
|
||||
elif myarg in ['custom', 'schemas']:
|
||||
getSchemas = True
|
||||
projection = 'custom'
|
||||
customFieldMask = sys.argv[i + 1]
|
||||
if not fieldsList:
|
||||
fieldsList = ['primaryEmail']
|
||||
fieldsList.append('customSchemas')
|
||||
if sys.argv[i + 1].lower() == 'all':
|
||||
projection = 'full'
|
||||
else:
|
||||
projection = 'custom'
|
||||
customFieldMask = sys.argv[i + 1].replace(' ', ',')
|
||||
i += 2
|
||||
elif myarg in USER_ARGUMENT_TO_PROPERTY_MAP:
|
||||
if not fieldsList:
|
||||
fieldsList = ['primaryEmail',]
|
||||
fieldsList.extend(USER_ARGUMENT_TO_PROPERTY_MAP[myarg])
|
||||
i += 1
|
||||
elif myarg == 'fields':
|
||||
if not fieldsList:
|
||||
fieldsList = ['primaryEmail',]
|
||||
fieldNameList = sys.argv[i + 1]
|
||||
for field in fieldNameList.lower().replace(',', ' ').split():
|
||||
if field in USER_ARGUMENT_TO_PROPERTY_MAP:
|
||||
fieldsList.extend(USER_ARGUMENT_TO_PROPERTY_MAP[field])
|
||||
else:
|
||||
controlflow.invalid_argument_exit(field,
|
||||
'gam info users fields')
|
||||
i += 2
|
||||
elif myarg == 'userview':
|
||||
viewType = 'domain_public'
|
||||
@@ -8831,6 +8885,7 @@ def doGetUserInfo(user_email=None):
|
||||
'get',
|
||||
userKey=user_email,
|
||||
projection=projection,
|
||||
fields=','.join(set(fieldsList)) if fieldsList else '*',
|
||||
customFieldMask=customFieldMask,
|
||||
viewType=viewType)
|
||||
print(f'User: {user["primaryEmail"]}')
|
||||
@@ -8839,14 +8894,7 @@ def doGetUserInfo(user_email=None):
|
||||
if 'name' in user and 'familyName' in user['name']:
|
||||
print(f'Last Name: {user["name"]["familyName"]}')
|
||||
if 'languages' in user:
|
||||
up = 'languageCode'
|
||||
languages = [row[up] for row in user['languages'] if up in row]
|
||||
if languages:
|
||||
print(f'Languages: {",".join(languages)}')
|
||||
up = 'customLanguage'
|
||||
languages = [row[up] for row in user['languages'] if up in row]
|
||||
if languages:
|
||||
print(f'Custom Languages: {",".join(languages)}')
|
||||
print(f"Languages: {_formatLanguagesList(user['languages'], ',')}")
|
||||
if 'isAdmin' in user:
|
||||
print(f'Is a Super Admin: {user["isAdmin"]}')
|
||||
if 'isDelegatedAdmin' in user:
|
||||
@@ -9661,6 +9709,7 @@ def doPrintUsers():
|
||||
customFieldMask = None
|
||||
sortHeaders = getGroupFeed = getLicenseFeed = email_parts = False
|
||||
viewType = deleted_only = orderBy = sortOrder = None
|
||||
orgUnitPath = orgUnitPathLower = None
|
||||
groupDelimiter = ' '
|
||||
licenseDelimiter = ','
|
||||
i = 3
|
||||
@@ -9690,7 +9739,7 @@ def doPrintUsers():
|
||||
projection = 'full'
|
||||
else:
|
||||
projection = 'custom'
|
||||
customFieldMask = sys.argv[i + 1]
|
||||
customFieldMask = sys.argv[i + 1].replace(' ', ',')
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
@@ -9725,19 +9774,19 @@ def doPrintUsers():
|
||||
elif myarg in ['query', 'queries']:
|
||||
queries = getQueries(myarg, sys.argv[i + 1])
|
||||
i += 2
|
||||
elif myarg == 'limittoou':
|
||||
orgUnitPath = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1], pathOnly=True)
|
||||
orgUnitPathLower = orgUnitPath.lower()
|
||||
i += 2
|
||||
elif myarg in USER_ARGUMENT_TO_PROPERTY_MAP:
|
||||
if not fieldsList:
|
||||
fieldsList = [
|
||||
'primaryEmail',
|
||||
]
|
||||
fieldsList = ['primaryEmail',]
|
||||
display.add_field_to_csv_file(myarg, USER_ARGUMENT_TO_PROPERTY_MAP,
|
||||
fieldsList, fieldsTitles, titles)
|
||||
i += 1
|
||||
elif myarg == 'fields':
|
||||
if not fieldsList:
|
||||
fieldsList = [
|
||||
'primaryEmail',
|
||||
]
|
||||
fieldsList = ['primaryEmail',]
|
||||
fieldNameList = sys.argv[i + 1]
|
||||
for field in fieldNameList.lower().replace(',', ' ').split():
|
||||
if field in USER_ARGUMENT_TO_PROPERTY_MAP:
|
||||
@@ -9760,10 +9809,21 @@ def doPrintUsers():
|
||||
else:
|
||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam print users')
|
||||
if fieldsList:
|
||||
if orgUnitPath is not None:
|
||||
fieldsList.append('orgUnitPath')
|
||||
fields = f'nextPageToken,users({",".join(set(fieldsList)).replace(".", "/")})'
|
||||
else:
|
||||
fields = None
|
||||
for query in queries:
|
||||
if orgUnitPath is not None:
|
||||
if query is not None and query.find(orgUnitPath) == -1:
|
||||
query += f" orgUnitPath='{orgUnitPath}'"
|
||||
else:
|
||||
if query is None:
|
||||
query = ''
|
||||
else:
|
||||
query += ' '
|
||||
query += f"orgUnitPath='{orgUnitPath}'"
|
||||
printGettingAllItems('Users', query)
|
||||
page_message = gapi.got_total_items_first_last_msg('Users')
|
||||
all_users = gapi.get_all_pages(cd.users(),
|
||||
@@ -9782,13 +9842,16 @@ def doPrintUsers():
|
||||
projection=projection,
|
||||
customFieldMask=customFieldMask)
|
||||
for user in all_users:
|
||||
if email_parts and ('primaryEmail' in user):
|
||||
user_email = user['primaryEmail']
|
||||
if user_email.find('@') != -1:
|
||||
user['primaryEmailLocal'], user[
|
||||
'primaryEmailDomain'] = splitEmailAddress(user_email)
|
||||
display.add_row_titles_to_csv_file(utils.flatten_json(user),
|
||||
csvRows, titles)
|
||||
if orgUnitPathLower is None or orgUnitPathLower == user.get('orgUnitPath', '').lower():
|
||||
if email_parts and ('primaryEmail' in user):
|
||||
user_email = user['primaryEmail']
|
||||
if user_email.find('@') != -1:
|
||||
user['primaryEmailLocal'], user[
|
||||
'primaryEmailDomain'] = splitEmailAddress(user_email)
|
||||
if 'languages' in user:
|
||||
user['languages'] = _formatLanguagesList(user.pop('languages'), ' ')
|
||||
display.add_row_titles_to_csv_file(utils.flatten_json(user),
|
||||
csvRows, titles)
|
||||
if sortHeaders:
|
||||
display.sort_csv_titles([
|
||||
'primaryEmail',
|
||||
@@ -10452,9 +10515,10 @@ OAUTH2_SCOPES = [
|
||||
'scopes': 'https://www.googleapis.com/auth/admin.directory.device.chromebrowsers',
|
||||
},
|
||||
{
|
||||
'name': 'Chrome Management API - read only',
|
||||
'name': 'Chrome Management API - read only (2 scopes)',
|
||||
'subscope': [],
|
||||
'scopes': ['https://www.googleapis.com/auth/chrome.management.reports.readonly'],
|
||||
'scopes': ['https://www.googleapis.com/auth/chrome.management.reports.readonly',
|
||||
'https://www.googleapis.com/auth/chrome.management.telemetry.readonly'],
|
||||
},
|
||||
{
|
||||
'name': 'Chrome Policy API',
|
||||
@@ -11640,6 +11704,8 @@ def ProcessGAMCommand(args):
|
||||
gapi_cloudidentity_groups.print_()
|
||||
elif argument == 'devices':
|
||||
gapi_cloudidentity_devices.print_()
|
||||
elif argument == 'crostelemetry':
|
||||
gapi_chromemanagement.printShowCrosTelemetry()
|
||||
elif argument in ['groupmembers', 'groupsmembers']:
|
||||
gapi_directory_groups.print_members()
|
||||
elif argument in ['cigroupmembers', 'cigroupsmembers']:
|
||||
@@ -11748,6 +11814,8 @@ def ProcessGAMCommand(args):
|
||||
gapi_chromepolicy.printshow_schemas()
|
||||
elif argument in ['chromepolicy', 'chromepolicies']:
|
||||
gapi_chromepolicy.printshow_policies()
|
||||
elif argument == 'crostelemetry':
|
||||
gapi_chromemanagement.printShowCrosTelemetry(True)
|
||||
else:
|
||||
controlflow.invalid_argument_exit(argument, 'gam show')
|
||||
sys.exit(0)
|
||||
|
||||
@@ -9,6 +9,7 @@ from gam.var import YYYYMMDD_FORMAT
|
||||
from gam import controlflow
|
||||
from gam import display
|
||||
from gam import gapi
|
||||
from gam import utils
|
||||
from gam.gapi.directory import orgunits as gapi_directory_orgunits
|
||||
from gam.gapi.directory.cros import _getFilterDate
|
||||
|
||||
@@ -201,6 +202,79 @@ def printAppDevices():
|
||||
display.write_csv_file(csvRows, titles, 'Chrome Installed Application Devices', todrive)
|
||||
|
||||
|
||||
def printShowCrosTelemetry(show=False):
|
||||
cm = build()
|
||||
parent = _get_customerid()
|
||||
todrive = False
|
||||
filter_ = None
|
||||
readMask = []
|
||||
diskpercentonly = False
|
||||
supported_readmask_values = list(cm._rootDesc['schemas']['GoogleChromeManagementV1TelemetryDevice']['properties'].keys())
|
||||
supported_readmask_values.sort()
|
||||
supported_readmask_map = {item.lower():item for item in supported_readmask_values}
|
||||
listLimit = 0
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'fields':
|
||||
field_list = sys.argv[i+1].lower().split(',')
|
||||
for field_item in field_list:
|
||||
if field_item not in supported_readmask_map:
|
||||
controlflow.expected_argument_exit('fields',
|
||||
', '.join(supported_readmask_values),
|
||||
field_item)
|
||||
else:
|
||||
readMask.append(supported_readmask_map[field_item])
|
||||
i += 2
|
||||
elif myarg == 'filter':
|
||||
filter_ = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg == 'storagepercentonly':
|
||||
diskpercentonly = True
|
||||
i += 1
|
||||
else:
|
||||
msg = f'{myarg} is not a valid argument to "gam print crostelemetry"'
|
||||
controlflow.system_error_exit(3, msg)
|
||||
if not readMask:
|
||||
readMask = ','.join(supported_readmask_values)
|
||||
else:
|
||||
if 'deviceId' not in readMask:
|
||||
readMask.append('deviceId')
|
||||
readMask = ','.join(readMask)
|
||||
gam.printGettingAllItems('Chrome Device Telemetry...', filter_)
|
||||
page_message = gapi.got_total_items_msg('Chrome Device Telemetry', '...\n')
|
||||
devices = gapi.get_all_pages(cm.customers().telemetry().devices(),
|
||||
'list',
|
||||
'devices',
|
||||
page_message=page_message,
|
||||
parent=parent,
|
||||
filter=filter_,
|
||||
readMask=readMask)
|
||||
for device in devices:
|
||||
if 'totalDiskBytes' in device.get('storageInfo', {}) and 'availableDiskBytes' in device.get('storageInfo', {}):
|
||||
disk_avail = int(device['storageInfo']['availableDiskBytes'])
|
||||
disk_size = int(device['storageInfo']['totalDiskBytes'])
|
||||
if diskpercentonly:
|
||||
device['storageInfo'] = {}
|
||||
device['storageInfo']['percentDiskFree'] = int((disk_avail / disk_size) * 100)
|
||||
device['storageInfo']['percentDiskUsed'] = 100 - device['storageInfo']['percentDiskFree']
|
||||
if show:
|
||||
for device in devices:
|
||||
display.print_json(device)
|
||||
print()
|
||||
print()
|
||||
else:
|
||||
csvRows = []
|
||||
titles = []
|
||||
for device in devices:
|
||||
display.add_row_titles_to_csv_file(utils.flatten_json(device),
|
||||
csvRows, titles)
|
||||
display.write_csv_file(csvRows, titles, 'Telemetry Devices', todrive)
|
||||
|
||||
|
||||
CHROME_VERSIONS_TITLES = [
|
||||
'version', 'count', 'channel', 'deviceOsVersion', 'system'
|
||||
]
|
||||
|
||||
@@ -12,6 +12,14 @@ from gam.gapi import errors as gapi_errors
|
||||
from gam.gapi import cloudidentity as gapi_cloudidentity
|
||||
from gam.gapi.directory import customer as gapi_directory_customer
|
||||
|
||||
# This allows easy switching between v1 and v1beta1
|
||||
# v1
|
||||
CIGROUP_API_BETA = 'cloudidentity'
|
||||
CIGROUP_MEMBERKEY = 'preferredMemberKey'
|
||||
# v1beta1
|
||||
#CIGROUP_API_BETA = 'cloudidentity_beta'
|
||||
#CIGROUP_MEMBERKEY = 'memberKey'
|
||||
|
||||
|
||||
def create():
|
||||
ci = gapi_cloudidentity.build()
|
||||
@@ -73,7 +81,7 @@ def delete():
|
||||
|
||||
|
||||
def info():
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
ci = gapi_cloudidentity.build(CIGROUP_API_BETA)
|
||||
group = gam.normalizeEmailAddressOrUID(sys.argv[3])
|
||||
getUsers = True
|
||||
getSecuritySettings = True
|
||||
@@ -126,7 +134,7 @@ def info():
|
||||
print(' Members:')
|
||||
for member in members:
|
||||
role = get_single_role(member.get('roles', [])).lower()
|
||||
email = member.get('preferredMemberKey', {}).get('id')
|
||||
email = member.get(CIGROUP_MEMBERKEY, {}).get('id')
|
||||
member_type = member.get('type', 'USER').lower()
|
||||
jc_string = ''
|
||||
if showJoinDate:
|
||||
@@ -155,7 +163,7 @@ def print_member_tree(ci, group_id, cached_group_members, spaces, show_role):
|
||||
for member in cached_group_members[group_id]:
|
||||
member_id = member.get('name', '')
|
||||
member_id = member_id.split('/')[-1]
|
||||
email = member.get('preferredMemberKey', {}).get('id')
|
||||
email = member.get(CIGROUP_MEMBERKEY, {}).get('id')
|
||||
member_type = member.get('type', 'USER').lower()
|
||||
if show_role:
|
||||
role = get_single_role(member.get('roles', [])).lower()
|
||||
@@ -197,7 +205,7 @@ GROUP_ROLES_MAP = {
|
||||
|
||||
|
||||
def print_():
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
ci = gapi_cloudidentity.build(CIGROUP_API_BETA)
|
||||
i = 3
|
||||
members = False
|
||||
membersCountOnly = False
|
||||
@@ -335,12 +343,12 @@ def print_():
|
||||
)
|
||||
page_message = gapi.got_total_items_first_last_msg('Members')
|
||||
validRoles, _, _ = gam._getRoleVerification(
|
||||
'.'.join(roles), 'nextPageToken,members(email,id,role)')
|
||||
','.join(roles), 'nextPageToken,members(email,id,role)')
|
||||
groupMembers = gapi.get_all_pages(ci.groups().memberships(),
|
||||
'list',
|
||||
'memberships',
|
||||
page_message=page_message,
|
||||
message_attribute=['preferredMemberKey', 'id'],
|
||||
message_attribute=[CIGROUP_MEMBERKEY, 'id'],
|
||||
soft_errors=True,
|
||||
parent=groupKey_id,
|
||||
view='BASIC')
|
||||
@@ -354,7 +362,7 @@ def print_():
|
||||
ownersList = []
|
||||
ownersCount = 0
|
||||
for member in groupMembers:
|
||||
member_email = member['preferredMemberKey']['id']
|
||||
member_email = member[CIGROUP_MEMBERKEY]['id']
|
||||
role = get_single_role(member.get('roles', []))
|
||||
if not validRoles or role in validRoles:
|
||||
if role == ROLE_MEMBER:
|
||||
@@ -447,7 +455,7 @@ def _get_groups_list(ci=None, member=None, parent=None):
|
||||
|
||||
|
||||
def get_membership_graph(member):
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
ci = gapi_cloudidentity.build(CIGROUP_API_BETA)
|
||||
query = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels"
|
||||
result = gapi.call(ci.groups().memberships(),
|
||||
'getMembershipGraph',
|
||||
@@ -457,7 +465,7 @@ def get_membership_graph(member):
|
||||
|
||||
|
||||
def print_members():
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
ci = gapi_cloudidentity.build(CIGROUP_API_BETA)
|
||||
todrive = False
|
||||
gapi_directory_customer.setTrueCustomerId()
|
||||
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||
@@ -514,8 +522,8 @@ def print_members():
|
||||
view='FULL',
|
||||
pageSize=500,
|
||||
page_message=page_message,
|
||||
message_attribute=['preferredMemberKey', 'id'])
|
||||
#fields='nextPageToken,memberships(preferredMemberKey,roles,createTime,updateTime)')
|
||||
message_attribute=[CIGROUP_MEMBERKEY, 'id'])
|
||||
#fields=f'nextPageToken,memberships({CIGROUP_MEMBERKEY},roles,createTime,updateTime)')
|
||||
if roles:
|
||||
group_members = filter_members_to_roles(group_members, roles)
|
||||
for member in group_members:
|
||||
@@ -573,7 +581,7 @@ def update():
|
||||
]
|
||||
return (role, expireTime, users_email)
|
||||
|
||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||
ci = gapi_cloudidentity.build(CIGROUP_API_BETA)
|
||||
group = sys.argv[3]
|
||||
myarg = sys.argv[4].lower()
|
||||
items = []
|
||||
@@ -600,7 +608,7 @@ def update():
|
||||
items.append(item)
|
||||
elif len(users_email) > 0:
|
||||
body = {
|
||||
'preferredMemberKey': {
|
||||
CIGROUP_MEMBERKEY: {
|
||||
'id': users_email[0]
|
||||
},
|
||||
'roles': [{
|
||||
@@ -820,12 +828,12 @@ def update():
|
||||
page_message=page_message,
|
||||
throw_reasons=gapi_errors.MEMBERS_THROW_REASONS,
|
||||
parent=parent,
|
||||
fields='nextPageToken,memberships(preferredMemberKey,roles)')
|
||||
fields=f'nextPageToken,memberships({CIGROUP_MEMBERKEY},roles)')
|
||||
result = filter_members_to_roles(result, roles)
|
||||
if not result:
|
||||
print('Group already has 0 members')
|
||||
return
|
||||
users_email = [member['preferredMemberKey']['id'] for member in result]
|
||||
users_email = [member[CIGROUP_MEMBERKEY]['id'] for member in result]
|
||||
sys.stderr.write(
|
||||
f'Group: {group}, Will remove {len(users_email)} {", ".join(roles).lower()}s.\n'
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ import platform
|
||||
import re
|
||||
|
||||
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
|
||||
GAM_VERSION = '6.11'
|
||||
GAM_VERSION = '6.12'
|
||||
GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
GAM_URL = 'https://git.io/gam'
|
||||
@@ -458,6 +458,7 @@ DRIVEFILE_FIELDS_CHOICES_MAP = {
|
||||
'createddate': 'createdDate',
|
||||
'createdtime': 'createdDate',
|
||||
'description': 'description',
|
||||
'driveid': 'driveId',
|
||||
'editable': 'editable',
|
||||
'explicitlytrashed': 'explicitlyTrashed',
|
||||
'fileextension': 'fileExtension',
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
yubikey-manager>=4.0.0
|
||||
cryptography
|
||||
distro; sys_platform == 'linux'
|
||||
filelock
|
||||
@@ -9,5 +10,4 @@ httplib2>=0.17.0
|
||||
importlib.metadata; python_version < '3.8'
|
||||
passlib>=1.7.2
|
||||
python-dateutil
|
||||
yubikey-manager>=4.0.0
|
||||
pathvalidate
|
||||
|
||||
Reference in New Issue
Block a user