Compare commits

...

71 Commits
v6.11 ... v6.12

Author SHA1 Message Date
Jay Lee
b158496bea Update build.yml 2021-12-28 11:16:23 -05:00
Jay Lee
a79b23e090 Update build.yml 2021-12-28 10:55:38 -05:00
Jay Lee
bdb56240f0 Update build.yml 2021-12-28 10:20:45 -05:00
Jay Lee
6dddf3eb30 Update build.yml 2021-12-28 10:20:25 -05:00
Jay Lee
7bd8569151 Update build.yml 2021-12-28 09:57:14 -05:00
Jay Lee
b03c9f1e35 Update build.yml 2021-12-28 09:47:54 -05:00
Jay Lee
057b5ff760 Update build.yml 2021-12-23 13:44:04 -05:00
Jay Lee
ba512b4159 Update build.yml 2021-12-23 13:39:00 -05:00
Jay Lee
a298aea2fe Update build.yml 2021-12-23 13:32:42 -05:00
Jay Lee
f433463074 Update build.yml 2021-12-23 13:24:01 -05:00
Jay Lee
afae08d6fe Update build.yml 2021-12-23 13:16:25 -05:00
Jay Lee
7cf2a08aff Update build.yml 2021-12-23 13:04:22 -05:00
Jay Lee
7df6781985 Update build.yml 2021-12-23 12:39:57 -05:00
Jay Lee
ae0f5e62e3 Update build.yml 2021-12-23 12:36:43 -05:00
Jay Lee
14c8356c6b Update build.yml 2021-12-23 12:23:37 -05:00
Jay Lee
45ffd4a793 Update build.yml 2021-12-23 12:16:37 -05:00
Jay Lee
eb8d39025e Update build.yml 2021-12-23 12:11:36 -05:00
Jay Lee
1f739e1c63 Update build.yml 2021-12-23 09:11:12 -05:00
Jay Lee
82111236fb Update build.yml 2021-12-23 08:32:32 -05:00
Jay Lee
813a94f8d6 Update build.yml 2021-12-23 08:28:15 -05:00
Jay Lee
e83b75e2c3 Update build.yml 2021-12-23 08:24:08 -05:00
Jay Lee
ce1e880ed0 Update build.yml 2021-12-23 08:19:11 -05:00
Jay Lee
427672065e Update build.yml 2021-12-23 07:24:25 -05:00
Jay Lee
055c5d5e54 Update build.yml 2021-12-22 20:22:05 -05:00
Jay Lee
4de7794e04 Update build.yml 2021-12-22 20:18:46 -05:00
Jay Lee
79686fd8ce Update build.yml 2021-12-22 20:06:18 -05:00
Jay Lee
cc5df0198b Update build.yml 2021-12-22 19:43:50 -05:00
Jay Lee
abc6e55ba7 Update build.yml 2021-12-22 19:31:03 -05:00
Jay Lee
0c8afb7fd6 Update build.yml 2021-12-22 19:26:36 -05:00
Jay Lee
c0c2cca44e Update build.yml 2021-12-22 19:01:16 -05:00
Jay Lee
faa645cb97 Update build.yml 2021-12-22 18:59:29 -05:00
Jay Lee
725c19aafc Update build.yml 2021-12-22 18:49:19 -05:00
Jay Lee
cc3b4c974d Update build.yml 2021-12-22 18:45:23 -05:00
Jay Lee
6ce64fad72 Update build.yml 2021-12-22 18:39:01 -05:00
Jay Lee
c1af67d4a3 Update build.yml 2021-12-22 18:35:59 -05:00
Jay Lee
802cb15007 Update requirements.txt 2021-12-22 16:46:04 -05:00
Jay Lee
b34bf3e56a Update linux-before-install.sh 2021-12-22 15:58:08 -05:00
Jay Lee
bf37700088 Update build.yml 2021-12-22 15:55:47 -05:00
Jay Lee
4a43ddfc25 Update build.yml 2021-12-22 15:51:03 -05:00
Jay Lee
650a1f5154 Update build.yml 2021-12-22 15:43:24 -05:00
Jay Lee
5eda7e30b0 Update build.yml 2021-12-22 13:10:09 -05:00
Jay Lee
8a26f547e5 Update build.yml 2021-12-22 10:25:41 -05:00
Jay Lee
343088913f Update build.yml 2021-12-22 09:56:08 -05:00
Jay Lee
5a0272fd5b Merge branch 'main' of github.com:jay0lee/GAM 2021-12-22 09:54:29 -05:00
Jay Lee
dc93503625 CrOS Telemetry API 2021-12-22 09:52:48 -05:00
Ross Scroggs
6ea6c0889b Fix show filelist query issue; add driveId to drive file fields (#1461)
* Fix show filelist query issue

If the user says: query "A or B" this becomes "'me' in owners and A or B" which is the same as "('me' in owners and A) or B" which gives incorrect results. The fix makes "'me' in owners and (A or B)"

* Add driveId to list of drive file fields
2021-12-17 11:51:32 -05:00
Jay Lee
99ab72df3f GAM 6.12 2021-12-16 07:41:30 -05:00
Ross Scroggs
99bda1385e languages update; fields for gam info user; cloud identity groups update to v1 (#1459)
* languages update

The API doesn't return languages unless you specifically mention in in fields list

* languages cleanup in print users

* Add fields to gam info user

* No up for languages

* Use v1 for Cloud Identity groups; fix bug in print cigroups member

* It's an error to set preference on custom language
2021-12-16 07:40:08 -05:00
Jay Lee
7ce3b4a8c0 Update build.yml 2021-12-15 12:06:46 -05:00
Jay Lee
495722d0d6 Update build.yml 2021-12-14 20:20:09 -05:00
Jay Lee
aca31be5d7 Update build.yml 2021-12-14 19:53:11 -05:00
Jay Lee
b9b7ae8d99 Merge branch 'main' of github.com:jay0lee/GAM 2021-12-09 15:27:38 -05:00
Jay Lee
0d46c1d13a Set user preferred language 2021-12-09 15:07:57 -05:00
Ross Scroggs
6b63ecdc19 Add limittoou <OrgUnitPath> to print users to allow selection of OU w/o children (#1455) 2021-12-09 10:35:47 -05:00
Jay Lee
f9ca0323a1 Update build.yml 2021-12-07 08:18:03 -05:00
Jay Lee
c50aa4d2e8 Update build.yml 2021-12-06 16:27:53 -05:00
Jay Lee
a72ded9079 wipe cache 2021-11-24 11:17:41 -05:00
Jay Lee
cbabbee075 wipe cache 2021-11-24 11:15:24 -05:00
Jay Lee
f55a344b7a Update build.yml 2021-11-23 10:48:03 -05:00
Jay Lee
d84f8418ff Update build.yml 2021-11-23 10:39:56 -05:00
Jay Lee
30c5e92de6 Update build.yml 2021-11-23 10:15:54 -05:00
Jay Lee
5f618a7f65 Update build.yml 2021-11-23 08:41:50 -05:00
Jay Lee
3e833419db Update README.md 2021-11-23 08:33:53 -05:00
Jay Lee
0d94bae0b5 Update README.md 2021-11-23 08:33:40 -05:00
Jay Lee
f5dec96ffb Update README.md 2021-11-23 08:31:32 -05:00
Jay Lee
e91d12caaf Update macos-install.sh 2021-11-23 08:26:12 -05:00
Jay Lee
fd5a1faa58 Update gam.spec 2021-11-22 16:31:44 -05:00
Jay Lee
90a9212793 Update build.yml 2021-11-22 16:28:10 -05:00
Jay Lee
7e582ac1fc Update build.yml 2021-11-22 16:19:34 -05:00
Jay Lee
65a740569c Update build.yml 2021-11-22 16:16:28 -05:00
Jay Lee
a47ef0e1f5 Update build.yml 2021-11-22 15:41:05 -05:00
11 changed files with 312 additions and 130 deletions

View File

@@ -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

View File

@@ -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`

View File

@@ -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

View File

@@ -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.
![Build Status](https://github.com/jay0lee/GAM/workflows/Build%20and%20test%20GAM/badge.svg)
@@ -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.

View File

@@ -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>

View File

@@ -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)

View File

@@ -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)

View File

@@ -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'
]

View File

@@ -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'
)

View File

@@ -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',

View File

@@ -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