mirror of
https://github.com/GAM-team/GAM.git
synced 2026-07-05 21:31:35 +00:00
Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6863ae2d6 | ||
|
|
36830250b5 | ||
|
|
4ca1c3537b | ||
|
|
eeab09eacb | ||
|
|
af16967257 | ||
|
|
75e2bf5a9a | ||
|
|
4db3bc409b | ||
|
|
32ccf414ea | ||
|
|
615e48fffc | ||
|
|
93bf3fce29 | ||
|
|
899601569a | ||
|
|
b1805b64a2 | ||
|
|
58190343b1 | ||
|
|
99d48b1939 | ||
|
|
82b66d53cb | ||
|
|
3200de56cc | ||
|
|
0a627d5c79 | ||
|
|
22399deb79 | ||
|
|
6a77617e3b | ||
|
|
2868ef99ae | ||
|
|
21557f9892 | ||
|
|
d2385ae62d | ||
|
|
a84efef389 | ||
|
|
310bcd1585 | ||
|
|
753f44deb2 | ||
|
|
df1f0f8f09 | ||
|
|
45e1b50674 | ||
|
|
0a2b048fb1 | ||
|
|
e3c5dca09d | ||
|
|
88339b7214 | ||
|
|
1f2bb18bc1 | ||
|
|
74977a6154 | ||
|
|
00413fe7a4 | ||
|
|
9bb9d331ad | ||
|
|
f022ffdff4 | ||
|
|
28dade2a34 | ||
|
|
7378b9d843 | ||
|
|
71075e95bf | ||
|
|
108990cf06 | ||
|
|
ebfdf4b052 | ||
|
|
dbf4073216 | ||
|
|
83214eaaf8 | ||
|
|
1100fdd456 | ||
|
|
481bfa5440 | ||
|
|
30282c7fbb | ||
|
|
382bc71b21 | ||
|
|
f3fba97652 | ||
|
|
7f51e35bd4 | ||
|
|
95beb8e62a | ||
|
|
1a9de867f9 | ||
|
|
b42946bbe1 | ||
|
|
40b2fd09ff |
2
.github/actions/linux-before-install.sh
vendored
2
.github/actions/linux-before-install.sh
vendored
@@ -94,7 +94,7 @@ else
|
|||||||
python=~/python/bin/python3
|
python=~/python/bin/python3
|
||||||
pip=~/python/bin/pip3
|
pip=~/python/bin/pip3
|
||||||
|
|
||||||
if ([ "${ImageOS}" == "ubuntu16" ]) && [ "${HOSTTYPE}" == "x86_64" ]; then
|
if ([ "${ImageOS}" == "ubuntu20" ]) && [ "${HOSTTYPE}" == "x86_64" ]; then
|
||||||
echo "Installing deps for StaticX..."
|
echo "Installing deps for StaticX..."
|
||||||
if [ ! -d patchelf-$PATCHELF_VERSION ]; then
|
if [ ! -d patchelf-$PATCHELF_VERSION ]; then
|
||||||
echo "Downloading PatchELF $PATCHELF_VERSION"
|
echo "Downloading PatchELF $PATCHELF_VERSION"
|
||||||
|
|||||||
6
.github/actions/linux-install.sh
vendored
6
.github/actions/linux-install.sh
vendored
@@ -17,10 +17,10 @@ tar -C ${distpath} --create --file $GAM_ARCHIVE --xz gam
|
|||||||
echo "PyInstaller GAM info:"
|
echo "PyInstaller GAM info:"
|
||||||
du -h $gam
|
du -h $gam
|
||||||
time $gam version extended
|
time $gam version extended
|
||||||
if ([ "${ImageOS}" == "ubuntu16" ]) && [ "${HOSTTYPE}" == "x86_64" ]; then
|
if ([ "${ImageOS}" == "ubuntu20" ]) && [ "${HOSTTYPE}" == "x86_64" ]; then
|
||||||
GAM_LEGACY_ARCHIVE=gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-legacy.tar.xz
|
GAM_LEGACY_ARCHIVE=gam-${GAMVERSION}-${GAMOS}-${PLATFORM}-legacy.tar.xz
|
||||||
$python -OO -m staticx -l /lib/x86_64-linux-gnu/libresolv.so.2 -l /lib/x86_64-linux-gnu/libnss_dns.so.2 $gam $gam-staticx
|
$python -OO -m staticx $gam $gam-staticx
|
||||||
strip $gam-staticx
|
#strip $gam-staticx
|
||||||
rm $gampath/gam
|
rm $gampath/gam
|
||||||
mv $gam-staticx $gam
|
mv $gam-staticx $gam
|
||||||
chmod 755 $gam
|
chmod 755 $gam
|
||||||
|
|||||||
46
.github/workflows/build.yml
vendored
46
.github/workflows/build.yml
vendored
@@ -12,13 +12,13 @@ defaults:
|
|||||||
working-directory: src
|
working-directory: src
|
||||||
|
|
||||||
env:
|
env:
|
||||||
BUILD_PYTHON_VERSION: "3.9.6"
|
BUILD_PYTHON_VERSION: "3.10.0"
|
||||||
MIN_PYTHON_VERSION: "3.9.6"
|
MIN_PYTHON_VERSION: "3.10.0"
|
||||||
BUILD_OPENSSL_VERSION: "1.1.1k"
|
BUILD_OPENSSL_VERSION: "1.1.1l"
|
||||||
MIN_OPENSSL_VERSION: "1.1.1k"
|
MIN_OPENSSL_VERSION: "1.1.1l"
|
||||||
PATCHELF_VERSION: "0.12"
|
PATCHELF_VERSION: "0.13"
|
||||||
# PYINSTALLER_VERSION can be full commit hash or version like v4.20
|
# PYINSTALLER_VERSION can be full commit hash or version like v4.20
|
||||||
PYINSTALLER_VERSION: "0f2b2e921433ab5a510c7efdb21d9c1d7cfbc645"
|
PYINSTALLER_VERSION: "1e6a8d53f150cf24b574c32085f3745cbd2afaa6"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -26,34 +26,29 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-16.04
|
- os: ubuntu-18.04
|
||||||
jid: 1
|
jid: 1
|
||||||
goal: "build"
|
goal: "build"
|
||||||
gamos: "linux"
|
gamos: "linux"
|
||||||
platform: "x86_64"
|
platform: "x86_64"
|
||||||
- os: ubuntu-18.04
|
- os: ubuntu-20.04
|
||||||
jid: 2
|
jid: 2
|
||||||
goal: "build"
|
goal: "build"
|
||||||
gamos: "linux"
|
gamos: "linux"
|
||||||
platform: "x86_64"
|
platform: "x86_64"
|
||||||
- os: ubuntu-20.04
|
|
||||||
jid: 3
|
|
||||||
goal: "build"
|
|
||||||
gamos: "linux"
|
|
||||||
platform: "x86_64"
|
|
||||||
- os: macos-11.0
|
- os: macos-11.0
|
||||||
jid: 12
|
jid: 3
|
||||||
goal: "build"
|
goal: "build"
|
||||||
gamos: "macos"
|
gamos: "macos"
|
||||||
platform: "universal2"
|
platform: "universal2"
|
||||||
- os: windows-2019
|
- os: windows-2019
|
||||||
jid: 5
|
jid: 4
|
||||||
goal: "build"
|
goal: "build"
|
||||||
gamos: "windows"
|
gamos: "windows"
|
||||||
pyarch: "x64"
|
pyarch: "x64"
|
||||||
platform: "x86_64"
|
platform: "x86_64"
|
||||||
- os: windows-2019
|
- os: windows-2019
|
||||||
jid: 6
|
jid: 5
|
||||||
goal: "build"
|
goal: "build"
|
||||||
gamos: "windows"
|
gamos: "windows"
|
||||||
platform: "x86"
|
platform: "x86"
|
||||||
@@ -61,25 +56,25 @@ jobs:
|
|||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
goal: "test"
|
goal: "test"
|
||||||
python: "3.6"
|
python: "3.6"
|
||||||
jid: 7
|
jid: 6
|
||||||
gamos: "linux"
|
gamos: "linux"
|
||||||
platform: "x86_64"
|
platform: "x86_64"
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
goal: "test"
|
goal: "test"
|
||||||
python: "3.7"
|
python: "3.7"
|
||||||
jid: 8
|
jid: 7
|
||||||
gamos: "linux"
|
gamos: "linux"
|
||||||
platform: "x86_64"
|
platform: "x86_64"
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
goal: "test"
|
goal: "test"
|
||||||
python: "3.8"
|
python: "3.8"
|
||||||
jid: 9
|
jid: 8
|
||||||
gamos: "linux"
|
gamos: "linux"
|
||||||
platform: "x86_64"
|
platform: "x86_64"
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
goal: test
|
goal: test
|
||||||
python: "3.10.0-beta.1"
|
python: "3.10.0"
|
||||||
jid: 10
|
jid: 9
|
||||||
gamos: linux
|
gamos: linux
|
||||||
platform: x86_64
|
platform: x86_64
|
||||||
|
|
||||||
@@ -97,7 +92,7 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
~/python
|
~/python
|
||||||
~/ssl
|
~/ssl
|
||||||
key: ${{ matrix.os }}-${{ matrix.jid }}-20210628
|
key: ${{ matrix.os }}-${{ matrix.jid }}-20211014
|
||||||
|
|
||||||
- name: Set env variables
|
- name: Set env variables
|
||||||
env:
|
env:
|
||||||
@@ -166,6 +161,7 @@ jobs:
|
|||||||
echo "pip=$pip" >> $GITHUB_ENV
|
echo "pip=$pip" >> $GITHUB_ENV
|
||||||
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> $GITHUB_ENV
|
echo "LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> $GITHUB_ENV
|
||||||
echo -e "Python: $python\nPip: $pip\nLD_LIB...: $LD_LIBRARY_PATH"
|
echo -e "Python: $python\nPip: $pip\nLD_LIB...: $LD_LIBRARY_PATH"
|
||||||
|
$pip install --upgrade pip
|
||||||
$pip install wheel
|
$pip install wheel
|
||||||
export url="https://codeload.github.com/pyinstaller/pyinstaller/tar.gz/${PYINSTALLER_VERSION}"
|
export url="https://codeload.github.com/pyinstaller/pyinstaller/tar.gz/${PYINSTALLER_VERSION}"
|
||||||
echo "Downloading ${url}"
|
echo "Downloading ${url}"
|
||||||
@@ -184,7 +180,8 @@ jobs:
|
|||||||
$python ./waf all $TARGETARCH
|
$python ./waf all $TARGETARCH
|
||||||
cd ..
|
cd ..
|
||||||
fi
|
fi
|
||||||
$python setup.py install
|
$pip install .
|
||||||
|
#$python setup.py install
|
||||||
#$pip install pyinstaller
|
#$pip install pyinstaller
|
||||||
|
|
||||||
- name: Install pip requirements
|
- name: Install pip requirements
|
||||||
@@ -218,6 +215,7 @@ jobs:
|
|||||||
- name: Basic Tests build jobs only
|
- name: Basic Tests build jobs only
|
||||||
if: matrix.goal != 'test'
|
if: matrix.goal != 'test'
|
||||||
run: |
|
run: |
|
||||||
|
$pip install packaging
|
||||||
export vline=$($gam version | grep "Python ")
|
export vline=$($gam version | grep "Python ")
|
||||||
export python_line=($vline)
|
export python_line=($vline)
|
||||||
export this_python=${python_line[1]}
|
export this_python=${python_line[1]}
|
||||||
@@ -259,6 +257,8 @@ jobs:
|
|||||||
$gam user $gam_user sendemail recipient $newuser subject "test message $newbase" message "GHA test message"
|
$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 user $gam_user sendemail recipient exchange@pdl.jaylee.us subject "test ${tstamp}" message "test message"
|
||||||
$gam create group $newgroup name "GHA $JID group" description "This is a description" isarchived true
|
$gam create group $newgroup name "GHA $JID group" description "This is a description" isarchived true
|
||||||
|
$gam update cigroup $newgroup memberrestriction 'member.type == 1 || member.customer_id == groupCustomerId()'
|
||||||
|
$gam info cigroup $newgroup
|
||||||
$gam user $newuser add license gsuitebusiness
|
$gam user $newuser add license gsuitebusiness
|
||||||
$gam update group $newgroup add owner $gam_user
|
$gam update group $newgroup add owner $gam_user
|
||||||
$gam update group $newgroup add member $newuser
|
$gam update group $newgroup add member $newuser
|
||||||
|
|||||||
27
README.md
27
README.md
@@ -1,23 +1,46 @@
|
|||||||
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 (fka G Suite) Administrators to manage domain and user settings quickly and easily.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
# Quick Start
|
# Quick Start
|
||||||
|
|
||||||
## Linux / MacOS
|
## Linux / MacOS
|
||||||
|
|
||||||
Open a terminal and run:
|
Open a terminal and run:
|
||||||
```
|
|
||||||
|
```sh
|
||||||
bash <(curl -s -S -L https://git.io/install-gam)
|
bash <(curl -s -S -L https://git.io/install-gam)
|
||||||
```
|
```
|
||||||
|
|
||||||
this will download GAM, install it and start setup.
|
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
|
## Windows
|
||||||
|
|
||||||
Download the MSI Installer from the [GitHub Releases] page. Install the MSI and you'll be prompted to setup GAM.
|
Download the MSI Installer from the [GitHub Releases] page. Install the MSI and you'll be prompted to setup GAM.
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
The GAM documentation is hosted in the [GitHub Wiki]
|
The GAM documentation is hosted in the [GitHub Wiki]
|
||||||
|
|
||||||
# Mailing List / Discussion group
|
# Mailing List / Discussion group
|
||||||
|
|
||||||
The GAM mailing list / discussion group is hosted on [Google Groups]. You can join the list and interact via email, or just post from the web itself.
|
The GAM mailing list / discussion group is hosted on [Google Groups]. You can join the list and interact via email, or just post from the web itself.
|
||||||
|
|
||||||
# Chat Room
|
# Chat Room
|
||||||
|
|
||||||
There is a public chat room hosted in Google Chat. [Instructions to join](https://git.io/gam-chat).
|
There is a public chat room hosted in Google Chat. [Instructions to join](https://git.io/gam-chat).
|
||||||
|
|
||||||
# Author
|
# Author
|
||||||
GAM is maintained by <a href="mailto:jay0lee@gmail.com">Jay Lee</a>. Please direct "how do I?" questions to [Google Groups].
|
|
||||||
|
GAM is maintained by [Jay Lee](mailto:jay0lee@gmail.com). Please direct "how do I?" questions to [Google Groups].
|
||||||
|
|
||||||
[GAM release]: https://git.io/gamreleases
|
[GAM release]: https://git.io/gamreleases
|
||||||
[GitHub Releases]: https://github.com/jay0lee/GAM/releases
|
[GitHub Releases]: https://github.com/jay0lee/GAM/releases
|
||||||
|
|||||||
@@ -222,8 +222,10 @@ If an item contains spaces, it should be surrounded by ".
|
|||||||
<QueryContact> ::= <String> See: https://developers.google.com/google-apps/contacts/v3/reference#contacts-query-parameters-reference
|
<QueryContact> ::= <String> See: https://developers.google.com/google-apps/contacts/v3/reference#contacts-query-parameters-reference
|
||||||
<QueryCrOS> ::= <String> See: https://support.google.com/chrome/a/answer/1698333?hl=en
|
<QueryCrOS> ::= <String> See: https://support.google.com/chrome/a/answer/1698333?hl=en
|
||||||
<QueryDriveFile> ::= <String> See: https://developers.google.com/drive/v2/web/search-parameters
|
<QueryDriveFile> ::= <String> See: https://developers.google.com/drive/v2/web/search-parameters
|
||||||
|
<QueryDynamicGroup> ::= <String> See: https://cloud.google.com/identity/docs/reference/rest/v1beta1/groups#dynamicgroupquery
|
||||||
<QueryGmail> ::= <String> See: https://support.google.com/mail/answer/7190
|
<QueryGmail> ::= <String> See: https://support.google.com/mail/answer/7190
|
||||||
<QueryGroup> ::= <String> See: https://developers.google.com/admin-sdk/directory/v1/guides/search-groups
|
<QueryGroup> ::= <String> See: https://developers.google.com/admin-sdk/directory/v1/guides/search-groups
|
||||||
|
<QueryMemberRestrictions> ::= <String> See: https://cloud.google.com/identity/docs/reference/rest/v1beta1/SecuritySettings#MemberRestriction
|
||||||
<QueryMobile> ::= <String> See: https://support.google.com/a/answer/7549103
|
<QueryMobile> ::= <String> See: https://support.google.com/a/answer/7549103
|
||||||
<QueryTeamDrive> ::= <String> See: https://developers.google.com/drive/api/v3/search-shareddrives
|
<QueryTeamDrive> ::= <String> See: https://developers.google.com/drive/api/v3/search-shareddrives
|
||||||
<QueryUser> ::= <String> See: https://developers.google.com/admin-sdk/directory/v1/guides/search-users
|
<QueryUser> ::= <String> See: https://developers.google.com/admin-sdk/directory/v1/guides/search-users
|
||||||
@@ -353,6 +355,7 @@ If an item contains spaces, it should be surrounded by ".
|
|||||||
shared|
|
shared|
|
||||||
sharedwithmedate|sharedwithmetime|
|
sharedwithmedate|sharedwithmetime|
|
||||||
sharinguser|
|
sharinguser|
|
||||||
|
shortcutdetails|
|
||||||
size|
|
size|
|
||||||
spaces|
|
spaces|
|
||||||
starred|
|
starred|
|
||||||
@@ -698,9 +701,10 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
|||||||
(contentrestrictions readonly true [reason <String>])|
|
(contentrestrictions readonly true [reason <String>])|
|
||||||
copyrequireswriterpermission|
|
copyrequireswriterpermission|
|
||||||
(lastviewedbyme <Time>)|(modifieddate|modifiedtime <Time>)|(description <String>)|(mimetype <MimeType>)|
|
(lastviewedbyme <Time>)|(modifieddate|modifiedtime <Time>)|(description <String>)|(mimetype <MimeType>)|
|
||||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|writerscanshare
|
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|
|
||||||
(securityupdate <Boolean>)|
|
(securityupdate <Boolean>)|
|
||||||
(shortcut <DriveFileID>)
|
(shortcut <DriveFileID>)|
|
||||||
|
writerscantshare|writerscanshare
|
||||||
<DriveFileUpdateAttribute> ::=
|
<DriveFileUpdateAttribute> ::=
|
||||||
(localfile <FileName>|-)|
|
(localfile <FileName>|-)|
|
||||||
(convert)|(ocr)|(ocrlanguage <Language>)|
|
(convert)|(ocr)|(ocrlanguage <Language>)|
|
||||||
@@ -709,9 +713,10 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
|||||||
(contentrestrictions readonly true [reason <String>])|
|
(contentrestrictions readonly true [reason <String>])|
|
||||||
(copyrequireswriterpermission <Boolean>)|
|
(copyrequireswriterpermission <Boolean>)|
|
||||||
(lastviewedbyme <Time>)|(modifieddate <Time>)|(description <String>)|(mimetype <MimeType>)|
|
(lastviewedbyme <Time>)|(modifieddate <Time>)|(description <String>)|(mimetype <MimeType>)|
|
||||||
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare|writerscanshare
|
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|
|
||||||
(securityupdate <Boolean>)|
|
(securityupdate <Boolean>)|
|
||||||
(shortcut <DriveFileID>)
|
(shortcut <DriveFileID>)|
|
||||||
|
writerscantshare|writerscanshare
|
||||||
<GroupSettingsAttribute> ::=
|
<GroupSettingsAttribute> ::=
|
||||||
(allowexternalmembers <Boolean>)|
|
(allowexternalmembers <Boolean>)|
|
||||||
(allowwebposting <Boolean>)|
|
(allowwebposting <Boolean>)|
|
||||||
@@ -719,6 +724,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
|||||||
(customfootertext <String>)|
|
(customfootertext <String>)|
|
||||||
(customreplyto <EmailAddress>)|
|
(customreplyto <EmailAddress>)|
|
||||||
(defaultmessagedenynotificationtext <String>)|
|
(defaultmessagedenynotificationtext <String>)|
|
||||||
|
(defaultsender default_self|group)|
|
||||||
(description <String>)|
|
(description <String>)|
|
||||||
(enablecollaborativeinbox|collaborative <Boolean>)|
|
(enablecollaborativeinbox|collaborative <Boolean>)|
|
||||||
(includeinglobaladdresslist|gal <Boolean>)|
|
(includeinglobaladdresslist|gal <Boolean>)|
|
||||||
@@ -796,7 +802,6 @@ Specify a collection of Users by directly specifying them or by specifiying item
|
|||||||
field <FieldName> (type bool|date|double|email|int64|phone|string) [multivalued|multivalue] [indexed] [restricted] [range <Number> <Number>] endfield
|
field <FieldName> (type bool|date|double|email|int64|phone|string) [multivalued|multivalue] [indexed] [restricted] [range <Number> <Number>] endfield
|
||||||
|
|
||||||
<UserBasicAttribute> ::=
|
<UserBasicAttribute> ::=
|
||||||
(agreed2terms|agreedtoterms <Boolean>)|
|
|
||||||
(changepassword|changepasswordatnextlogin <Boolean>)|
|
(changepassword|changepasswordatnextlogin <Boolean>)|
|
||||||
(base64-md5|base64-sha1|crypt|sha|sha1|sha-1|md5|nohash)|
|
(base64-md5|base64-sha1|crypt|sha|sha1|sha-1|md5|nohash)|
|
||||||
(customerid <String>)|
|
(customerid <String>)|
|
||||||
@@ -1381,18 +1386,21 @@ gam print printermodels [todrive] [filter <String>]
|
|||||||
|
|
||||||
gam create cigroup <EmailAddress> <CIGroupAttribute>*
|
gam create cigroup <EmailAddress> <CIGroupAttribute>*
|
||||||
[makeowner] [alias|aliases <AliasList>] [dynamic <QueryDynamicGroup>]
|
[makeowner] [alias|aliases <AliasList>] [dynamic <QueryDynamicGroup>]
|
||||||
gam update cigroup <GroupItem> [email <EmailAddress>] <CIGroupAttribute>* [security]
|
gam update cigroup <GroupItem> [email <EmailAddress>] <CIGroupAttribute>*
|
||||||
|
[security] [dynamic <QueryDynamicGroup>]
|
||||||
|
[memberrestrictions <QueryMemberRestrictions>]
|
||||||
gam update cigroup <GroupItem> add [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
|
gam update cigroup <GroupItem> add [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
|
||||||
gam update cigroup <GroupItem> delete|remove [owner|manager|member] [notsuspended|suspended] <UserTypeEntity>
|
gam update cigroup <GroupItem> delete|remove [owner|manager|member] [notsuspended|suspended] <UserTypeEntity>
|
||||||
gam update cigroup <GroupItem> sync [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
|
gam update cigroup <GroupItem> sync [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
|
||||||
gam update cigroup <GroupItem> update [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
|
gam update cigroup <GroupItem> update [owner|manager|member] [notsuspended|suspended] [expires never|<Time>] <UserTypeEntity>
|
||||||
gam update cigroup <GroupItem> clear [member] [manager] [owner] [notsuspended|suspended]
|
gam update cigroup <GroupItem> clear [member] [manager] [owner] [notsuspended|suspended]
|
||||||
gam delete cigroup <GroupItem>
|
gam delete cigroup <GroupItem>
|
||||||
gam info cigroup <GroupItem> [nousers] [nojoindate] [showupdatedate] [membertree]
|
gam info cigroup <GroupItem> [nousers] [nojoindate] [showupdatedate] [membertree] [nosecurity|nosecuritysettings]
|
||||||
|
|
||||||
gam print cigroups [todrive]
|
gam print cigroups [todrive]
|
||||||
[enterprisemember <UserItem>]
|
[enterprisemember <UserItem>]
|
||||||
[members|memberscount] [managers|managerscount] [owners|ownerscount]
|
[members|memberscount] [managers|managerscount] [owners|ownerscount]
|
||||||
|
[memberrestrictions]
|
||||||
[delimiter <Character>] [sortheaders]
|
[delimiter <Character>] [sortheaders]
|
||||||
|
|
||||||
gam info cimember <UserItem> <GroupItem>
|
gam info cimember <UserItem> <GroupItem>
|
||||||
|
|||||||
@@ -538,6 +538,7 @@
|
|||||||
},
|
},
|
||||||
"CreateEnrollmentTokenRequest": {
|
"CreateEnrollmentTokenRequest": {
|
||||||
"id": "CreateEnrollmentTokenRequest",
|
"id": "CreateEnrollmentTokenRequest",
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"org_unit_path": {
|
"org_unit_path": {
|
||||||
"description": "The full path of the organizational unit or its unique ID.",
|
"description": "The full path of the organizational unit or its unique ID.",
|
||||||
@@ -548,6 +549,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"token_type": {
|
"token_type": {
|
||||||
|
"id": "token_type",
|
||||||
"annotations": {
|
"annotations": {
|
||||||
"required": [
|
"required": [
|
||||||
"cbcm.enrollmentTokens.create"
|
"cbcm.enrollmentTokens.create"
|
||||||
@@ -559,6 +561,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"MoveChromeBrowsersRequest": {
|
"MoveChromeBrowsersRequest": {
|
||||||
|
"id": "MoveChromeBrowsersRequest",
|
||||||
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"org_unit_path": {
|
"org_unit_path": {
|
||||||
"annotations": {
|
"annotations": {
|
||||||
@@ -576,7 +580,10 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"description": "List of unique device IDs of Chrome Browser Devices to move. A maximum of 600 browsers may be moved per request.",
|
"description": "List of unique device IDs of Chrome Browser Devices to move. A maximum of 600 browsers may be moved per request.",
|
||||||
"type": "array"
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ upgrade_only=false
|
|||||||
gamversion="latest"
|
gamversion="latest"
|
||||||
adminuser=""
|
adminuser=""
|
||||||
regularuser=""
|
regularuser=""
|
||||||
gam_glibc_vers="2.31 2.27 2.23"
|
gam_glibc_vers="2.31 2.27"
|
||||||
#gam_macos_vers="10.15.6 10.14.6 10.13.6"
|
#gam_macos_vers="10.15.6 10.14.6 10.13.6"
|
||||||
|
|
||||||
while getopts "hd:a:o:b:lp:u:r:v:" OPTION
|
while getopts "hd:a:o:b:lp:u:r:v:" OPTION
|
||||||
@@ -128,7 +128,7 @@ case $gamos in
|
|||||||
this_macos_ver=$osversion
|
this_macos_ver=$osversion
|
||||||
fi
|
fi
|
||||||
echo "You are running MacOS $this_macos_ver"
|
echo "You are running MacOS $this_macos_ver"
|
||||||
gamfile="macos-x86_64.tar.xz"
|
gamfile="macos-universal2.tar.xz"
|
||||||
;;
|
;;
|
||||||
MINGW64_NT*)
|
MINGW64_NT*)
|
||||||
gamos="windows"
|
gamos="windows"
|
||||||
|
|||||||
@@ -8,4 +8,4 @@ from gam.__main__ import main
|
|||||||
|
|
||||||
# Run from command line
|
# Run from command line
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main(sys.argv)
|
main()
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import http.client as http_client
|
|||||||
from multiprocessing import Pool as mp_pool
|
from multiprocessing import Pool as mp_pool
|
||||||
from multiprocessing import Lock as mp_lock
|
from multiprocessing import Lock as mp_lock
|
||||||
from urllib.parse import quote, urlencode, urlparse
|
from urllib.parse import quote, urlencode, urlparse
|
||||||
|
from pathvalidate import sanitize_filename
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
|
|
||||||
import googleapiclient
|
import googleapiclient
|
||||||
@@ -549,6 +550,7 @@ def SetGlobalVariables():
|
|||||||
filePresentValue=4,
|
filePresentValue=4,
|
||||||
fileAbsentValue=0)
|
fileAbsentValue=0)
|
||||||
_getOldSignalFile(GC_NO_BROWSER, 'nobrowser.txt')
|
_getOldSignalFile(GC_NO_BROWSER, 'nobrowser.txt')
|
||||||
|
_getOldSignalFile(GC_NO_TDEMAIL, 'notdemail.txt')
|
||||||
_getOldSignalFile(GC_OAUTH_BROWSER, 'oauthbrowser.txt')
|
_getOldSignalFile(GC_OAUTH_BROWSER, 'oauthbrowser.txt')
|
||||||
# _getOldSignalFile(GC_NO_CACHE, u'nocache.txt')
|
# _getOldSignalFile(GC_NO_CACHE, u'nocache.txt')
|
||||||
# _getOldSignalFile(GC_CACHE_DISCOVERY_ONLY, u'allcache.txt', filePresentValue=False, fileAbsentValue=True)
|
# _getOldSignalFile(GC_CACHE_DISCOVERY_ONLY, u'allcache.txt', filePresentValue=False, fileAbsentValue=True)
|
||||||
@@ -728,8 +730,12 @@ def getOSPlatform():
|
|||||||
elif myos == 'Darwin':
|
elif myos == 'Darwin':
|
||||||
myos = 'MacOS'
|
myos = 'MacOS'
|
||||||
mac_ver = platform.mac_ver()[0]
|
mac_ver = platform.mac_ver()[0]
|
||||||
|
major_ver = int(mac_ver.split('.')[0]) # macver 10.14.6 == major_ver 10
|
||||||
minor_ver = int(mac_ver.split('.')[1]) # macver 10.14.6 == minor_ver 14
|
minor_ver = int(mac_ver.split('.')[1]) # macver 10.14.6 == minor_ver 14
|
||||||
codename = MACOS_CODENAMES.get(minor_ver, '')
|
if major_ver == 10:
|
||||||
|
codename = MACOS_CODENAMES[major_ver].get(minor_ver, '')
|
||||||
|
else:
|
||||||
|
codename = MACOS_CODENAMES.get(major_ver, '')
|
||||||
pltfrm = ' '.join([codename, mac_ver])
|
pltfrm = ' '.join([codename, mac_ver])
|
||||||
else:
|
else:
|
||||||
pltfrm = platform.platform()
|
pltfrm = platform.platform()
|
||||||
@@ -1240,9 +1246,8 @@ def doCheckServiceAccount(users):
|
|||||||
'get',
|
'get',
|
||||||
name=name,
|
name=name,
|
||||||
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_THREE])
|
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_THREE])
|
||||||
# Both Google and GAM set key valid after to day before creation
|
|
||||||
key_created = dateutil.parser.parse(
|
key_created = dateutil.parser.parse(
|
||||||
key['validAfterTime'], ignoretz=True) + datetime.timedelta(days=1)
|
key['validAfterTime'], ignoretz=True)
|
||||||
key_age = datetime.datetime.now() - key_created
|
key_age = datetime.datetime.now() - key_created
|
||||||
key_days = key_age.days
|
key_days = key_age.days
|
||||||
if key_days > 30:
|
if key_days > 30:
|
||||||
@@ -1757,8 +1762,8 @@ def doCreateAdmin():
|
|||||||
def doPrintAdmins():
|
def doPrintAdmins():
|
||||||
cd = buildGAPIObject('directory')
|
cd = buildGAPIObject('directory')
|
||||||
roleId = None
|
roleId = None
|
||||||
userKey = None
|
|
||||||
todrive = False
|
todrive = False
|
||||||
|
kwargs = {}
|
||||||
fields = 'nextPageToken,items(roleAssignmentId,roleId,assignedTo,scopeType,orgUnitId)'
|
fields = 'nextPageToken,items(roleAssignmentId,roleId,assignedTo,scopeType,orgUnitId)'
|
||||||
titles = [
|
titles = [
|
||||||
'roleAssignmentId', 'roleId', 'role', 'assignedTo', 'assignedToUser',
|
'roleAssignmentId', 'roleId', 'role', 'assignedTo', 'assignedToUser',
|
||||||
@@ -1769,7 +1774,7 @@ def doPrintAdmins():
|
|||||||
while i < len(sys.argv):
|
while i < len(sys.argv):
|
||||||
myarg = sys.argv[i].lower()
|
myarg = sys.argv[i].lower()
|
||||||
if myarg == 'user':
|
if myarg == 'user':
|
||||||
userKey = normalizeEmailAddressOrUID(sys.argv[i + 1])
|
kwargs['userKey'] = normalizeEmailAddressOrUID(sys.argv[i + 1])
|
||||||
i += 2
|
i += 2
|
||||||
elif myarg == 'role':
|
elif myarg == 'role':
|
||||||
roleId = getRoleId(sys.argv[i + 1])
|
roleId = getRoleId(sys.argv[i + 1])
|
||||||
@@ -1779,14 +1784,18 @@ def doPrintAdmins():
|
|||||||
i += 1
|
i += 1
|
||||||
else:
|
else:
|
||||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam print admins')
|
controlflow.invalid_argument_exit(sys.argv[i], 'gam print admins')
|
||||||
|
if roleId and not kwargs:
|
||||||
|
kwargs['roleId'] = roleId
|
||||||
|
roleId = None
|
||||||
admins = gapi.get_all_pages(cd.roleAssignments(),
|
admins = gapi.get_all_pages(cd.roleAssignments(),
|
||||||
'list',
|
'list',
|
||||||
'items',
|
'items',
|
||||||
customer=GC_Values[GC_CUSTOMER_ID],
|
customer=GC_Values[GC_CUSTOMER_ID],
|
||||||
userKey=userKey,
|
fields=fields,
|
||||||
roleId=roleId,
|
**kwargs)
|
||||||
fields=fields)
|
|
||||||
for admin in admins:
|
for admin in admins:
|
||||||
|
if roleId and roleId != admin['roleId']:
|
||||||
|
continue
|
||||||
admin_attrib = {}
|
admin_attrib = {}
|
||||||
for key, value in list(admin.items()):
|
for key, value in list(admin.items()):
|
||||||
if key == 'assignedTo':
|
if key == 'assignedTo':
|
||||||
@@ -4057,8 +4066,7 @@ def downloadDriveFile(users):
|
|||||||
if targetName:
|
if targetName:
|
||||||
safe_file_title = targetName
|
safe_file_title = targetName
|
||||||
else:
|
else:
|
||||||
safe_file_title = ''.join(c for c in result['title']
|
safe_file_title = sanitize_filename(result['title'])
|
||||||
if c in FILENAME_SAFE_CHARS)
|
|
||||||
if not safe_file_title:
|
if not safe_file_title:
|
||||||
safe_file_title = fileId
|
safe_file_title = fileId
|
||||||
filename = os.path.join(targetFolder, safe_file_title)
|
filename = os.path.join(targetFolder, safe_file_title)
|
||||||
@@ -6669,12 +6677,12 @@ def getUserAttributes(i, cd, updateCmd):
|
|||||||
body['changePasswordAtNextLogin'] = getBoolean(
|
body['changePasswordAtNextLogin'] = getBoolean(
|
||||||
sys.argv[i + 1], myarg)
|
sys.argv[i + 1], myarg)
|
||||||
i += 2
|
i += 2
|
||||||
elif myarg == 'ipwhitelisted':
|
|
||||||
body['ipWhitelisted'] = getBoolean(sys.argv[i + 1], myarg)
|
|
||||||
i += 2
|
|
||||||
elif myarg == 'agreedtoterms':
|
elif myarg == 'agreedtoterms':
|
||||||
body['agreedToTerms'] = getBoolean(sys.argv[i + 1], myarg)
|
body['agreedToTerms'] = getBoolean(sys.argv[i + 1], myarg)
|
||||||
i += 2
|
i += 2
|
||||||
|
elif myarg == 'ipwhitelisted':
|
||||||
|
body['ipWhitelisted'] = getBoolean(sys.argv[i + 1], myarg)
|
||||||
|
i += 2
|
||||||
elif myarg in ['org', 'ou']:
|
elif myarg in ['org', 'ou']:
|
||||||
body['orgUnitPath'] = gapi_directory_orgunits.getOrgUnitItem(
|
body['orgUnitPath'] = gapi_directory_orgunits.getOrgUnitItem(
|
||||||
sys.argv[i + 1], pathOnly=True)
|
sys.argv[i + 1], pathOnly=True)
|
||||||
@@ -7236,6 +7244,7 @@ def enableGAMProjectAPIs(GAMProjectAPIs,
|
|||||||
gapi_errors.ErrorReason.FORBIDDEN,
|
gapi_errors.ErrorReason.FORBIDDEN,
|
||||||
gapi_errors.ErrorReason.PERMISSION_DENIED
|
gapi_errors.ErrorReason.PERMISSION_DENIED
|
||||||
],
|
],
|
||||||
|
retry_reasons=[gapi_errors.ErrorReason.INTERNAL_SERVER_ERROR],
|
||||||
name=service_name)
|
name=service_name)
|
||||||
print(f' API: {api}, Enabled{currentCount(j, jcount)}')
|
print(f' API: {api}, Enabled{currentCount(j, jcount)}')
|
||||||
break
|
break
|
||||||
@@ -7756,11 +7765,9 @@ def _generatePrivateKeyAndPublicCert(client_id, key_size):
|
|||||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, client_id)]))
|
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, client_id)]))
|
||||||
builder = builder.issuer_name(
|
builder = builder.issuer_name(
|
||||||
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, client_id)]))
|
x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, client_id)]))
|
||||||
not_valid_before = datetime.datetime.today() - datetime.timedelta(days=1)
|
builder = builder.not_valid_before(datetime.datetime.today())
|
||||||
not_valid_after = datetime.datetime.today() + datetime.timedelta(
|
# Google uses 12/31/9999 date for end time
|
||||||
days=365 * 10 - 1)
|
builder = builder.not_valid_after(datetime.datetime(9999, 12, 31, 23, 59))
|
||||||
builder = builder.not_valid_before(not_valid_before)
|
|
||||||
builder = builder.not_valid_after(not_valid_after)
|
|
||||||
builder = builder.serial_number(x509.random_serial_number())
|
builder = builder.serial_number(x509.random_serial_number())
|
||||||
builder = builder.public_key(public_key)
|
builder = builder.public_key(public_key)
|
||||||
builder = builder.add_extension(x509.BasicConstraints(ca=False,
|
builder = builder.add_extension(x509.BasicConstraints(ca=False,
|
||||||
@@ -7943,10 +7950,18 @@ def doCreateOrRotateServiceAccountKeys(iam=None,
|
|||||||
name=sa_name,
|
name=sa_name,
|
||||||
body={'publicKeyData': publicKeyData})
|
body={'publicKeyData': publicKeyData})
|
||||||
break
|
break
|
||||||
except googleapiclient.errors.HttpError:
|
except googleapiclient.errors.HttpError as err:
|
||||||
|
if hasattr(err, 'error_details') and \
|
||||||
|
err.error_details == 'The given public key already exists.':
|
||||||
print('WARNING: that key already exists.')
|
print('WARNING: that key already exists.')
|
||||||
result = {'name': oldPrivateKeyId}
|
result = {'name': oldPrivateKeyId}
|
||||||
break
|
break
|
||||||
|
elif hasattr(err, 'error_details'):
|
||||||
|
controlflow.system_error_exit(
|
||||||
|
4, err.error_details)
|
||||||
|
else:
|
||||||
|
controlflow.system_error_exit(
|
||||||
|
4, err)
|
||||||
except gapi_errors.GapiNotFoundError as e:
|
except gapi_errors.GapiNotFoundError as e:
|
||||||
if i == max_retries:
|
if i == max_retries:
|
||||||
raise e
|
raise e
|
||||||
@@ -9666,6 +9681,8 @@ def doPrintUsers():
|
|||||||
sortHeaders = True
|
sortHeaders = True
|
||||||
i += 1
|
i += 1
|
||||||
elif myarg in ['custom', 'schemas']:
|
elif myarg in ['custom', 'schemas']:
|
||||||
|
if not fieldsList:
|
||||||
|
fieldsList = ['primaryEmail']
|
||||||
fieldsList.append('customSchemas')
|
fieldsList.append('customSchemas')
|
||||||
if sys.argv[i + 1].lower() == 'all':
|
if sys.argv[i + 1].lower() == 'all':
|
||||||
projection = 'full'
|
projection = 'full'
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from gam import controlflow
|
|||||||
import gam
|
import gam
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main():
|
||||||
freeze_support()
|
freeze_support()
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
# https://bugs.python.org/issue33725 in Python 3.8.0 seems
|
# https://bugs.python.org/issue33725 in Python 3.8.0 seems
|
||||||
@@ -47,4 +47,4 @@ def main(argv):
|
|||||||
|
|
||||||
# Run from command line
|
# Run from command line
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main(sys.argv)
|
main()
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from threading import Timer
|
|||||||
|
|
||||||
from cryptography.hazmat.primitives import hashes, serialization
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric import padding
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
from smartcard.Exceptions import CardConnectionException
|
||||||
from ykman.device import connect_to_device
|
from ykman.device import connect_to_device
|
||||||
from ykman.piv import generate_self_signed_certificate, \
|
from ykman.piv import generate_self_signed_certificate, \
|
||||||
generate_chuid
|
generate_chuid
|
||||||
@@ -46,7 +47,10 @@ class YubiKey():
|
|||||||
self.key_id = service_account_info.get('private_key_id')
|
self.key_id = service_account_info.get('private_key_id')
|
||||||
|
|
||||||
def _connect(self):
|
def _connect(self):
|
||||||
|
try:
|
||||||
conn, _, _ = connect_to_device(self.serial_number)
|
conn, _, _ = connect_to_device(self.serial_number)
|
||||||
|
except CardConnectionException as err:
|
||||||
|
controlflow.system_error_exit(9, f'YubiKey - {err}')
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
def get_certificate(self):
|
def get_certificate(self):
|
||||||
@@ -62,7 +66,7 @@ class YubiKey():
|
|||||||
try:
|
try:
|
||||||
cert = session.get_certificate(self.slot)
|
cert = session.get_certificate(self.slot)
|
||||||
except ApduError as err:
|
except ApduError as err:
|
||||||
controlflow.system_error_exit(9, f'Yubikey = {err}')
|
controlflow.system_error_exit(9, f'YubiKey - {err}')
|
||||||
cert_pem = cert.public_bytes(
|
cert_pem = cert.public_bytes(
|
||||||
serialization.Encoding.PEM).decode()
|
serialization.Encoding.PEM).decode()
|
||||||
publicKeyData = b64encode(cert_pem.encode())
|
publicKeyData = b64encode(cert_pem.encode())
|
||||||
@@ -78,7 +82,7 @@ class YubiKey():
|
|||||||
_, _, info = connect_to_device(self.serial_number)
|
_, _, info = connect_to_device(self.serial_number)
|
||||||
return info.serial
|
return info.serial
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
controlflow.system_error_exit(9, f'YubikKey = {err}')
|
controlflow.system_error_exit(9, f'YubiKey - {err}')
|
||||||
|
|
||||||
def reset_piv(self):
|
def reset_piv(self):
|
||||||
'''Resets YubiKey PIV app and generates new key for GAM to use.'''
|
'''Resets YubiKey PIV app and generates new key for GAM to use.'''
|
||||||
@@ -101,7 +105,7 @@ class YubiKey():
|
|||||||
DEFAULT_MANAGEMENT_KEY)
|
DEFAULT_MANAGEMENT_KEY)
|
||||||
|
|
||||||
piv.verify_pin(new_pin)
|
piv.verify_pin(new_pin)
|
||||||
print('Yubikey is generating a non-exportable private key...')
|
print('YubiKey is generating a non-exportable private key...')
|
||||||
pubkey = piv.generate_key(SLOT.AUTHENTICATION,
|
pubkey = piv.generate_key(SLOT.AUTHENTICATION,
|
||||||
KEY_TYPE.RSA2048,
|
KEY_TYPE.RSA2048,
|
||||||
PIN_POLICY.ALWAYS,
|
PIN_POLICY.ALWAYS,
|
||||||
@@ -123,7 +127,7 @@ class YubiKey():
|
|||||||
piv.put_object(OBJECT_ID.CHUID,
|
piv.put_object(OBJECT_ID.CHUID,
|
||||||
generate_chuid())
|
generate_chuid())
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
controlflow.system_error_exit(8, f'Yubikey - {err}')
|
controlflow.system_error_exit(8, f'YubiKey - {err}')
|
||||||
|
|
||||||
|
|
||||||
def sign(self, message):
|
def sign(self, message):
|
||||||
@@ -145,7 +149,7 @@ class YubiKey():
|
|||||||
hash_algorithm=hashes.SHA256(),
|
hash_algorithm=hashes.SHA256(),
|
||||||
padding=padding.PKCS1v15())
|
padding=padding.PKCS1v15())
|
||||||
except ApduError as err:
|
except ApduError as err:
|
||||||
controlflow.system_error_exit(8, f'YubiKey = {err}')
|
controlflow.system_error_exit(8, f'YubiKey - {err}')
|
||||||
except ValueError as err:
|
except ValueError as err:
|
||||||
controlflow.system_error_exit(9, f'YubiKey - {err}')
|
controlflow.system_error_exit(9, f'YubiKey - {err}')
|
||||||
if 'mplock' in globals():
|
if 'mplock' in globals():
|
||||||
|
|||||||
@@ -158,25 +158,25 @@ def write_csv_file(csvRows, titles, list_type, todrive):
|
|||||||
for c, filterVal in iter(filters.items()):
|
for c, filterVal in iter(filters.items()):
|
||||||
for column in columns[c]:
|
for column in columns[c]:
|
||||||
if filterVal[1] == 'regex':
|
if filterVal[1] == 'regex':
|
||||||
if filterVal[2].search(str(row.get(column, ''))):
|
|
||||||
return True
|
|
||||||
elif filterVal[1] == 'notregex':
|
|
||||||
if not filterVal[2].search(str(row.get(column, ''))):
|
if not filterVal[2].search(str(row.get(column, ''))):
|
||||||
return True
|
return False
|
||||||
|
elif filterVal[1] == 'notregex':
|
||||||
|
if filterVal[2].search(str(row.get(column, ''))):
|
||||||
|
return False
|
||||||
elif filterVal[1] in ['date', 'time']:
|
elif filterVal[1] in ['date', 'time']:
|
||||||
if rowDateTimeFilterMatch(
|
if not rowDateTimeFilterMatch(
|
||||||
filterVal[1] == 'date', row.get(column, ''),
|
filterVal[1] == 'date', row.get(column, ''),
|
||||||
filterVal[2], filterVal[3]):
|
filterVal[2], filterVal[3]):
|
||||||
return True
|
|
||||||
elif filterVal[1] == 'count':
|
|
||||||
if rowCountFilterMatch(
|
|
||||||
row.get(column, 0), filterVal[2], filterVal[3]):
|
|
||||||
return True
|
|
||||||
else: #boolean
|
|
||||||
if rowBooleanFilterMatch(
|
|
||||||
row.get(column, False), filterVal[2]):
|
|
||||||
return True
|
|
||||||
return False
|
return False
|
||||||
|
elif filterVal[1] == 'count':
|
||||||
|
if not rowCountFilterMatch(
|
||||||
|
row.get(column, 0), filterVal[2], filterVal[3]):
|
||||||
|
return False
|
||||||
|
else: #boolean
|
||||||
|
if not rowBooleanFilterMatch(
|
||||||
|
row.get(column, False), filterVal[2]):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
if GC_Values[GC_CSV_ROW_FILTER] or GC_Values[GC_CSV_ROW_DROP_FILTER]:
|
if GC_Values[GC_CSV_ROW_FILTER] or GC_Values[GC_CSV_ROW_DROP_FILTER]:
|
||||||
if GC_Values[GC_CSV_ROW_FILTER]:
|
if GC_Values[GC_CSV_ROW_FILTER]:
|
||||||
@@ -283,6 +283,7 @@ and follow recommend steps to authorize GAM for Drive access.''')
|
|||||||
if GC_Values[GC_NO_BROWSER]:
|
if GC_Values[GC_NO_BROWSER]:
|
||||||
msg_txt = f'Drive file uploaded to:\n {file_url}'
|
msg_txt = f'Drive file uploaded to:\n {file_url}'
|
||||||
msg_subj = f'{GC_Values[GC_DOMAIN]} - {list_type}'
|
msg_subj = f'{GC_Values[GC_DOMAIN]} - {list_type}'
|
||||||
|
if not GC_Values[GC_NO_TDEMAIL]:
|
||||||
gam.send_email(msg_subj, msg_txt)
|
gam.send_email(msg_subj, msg_txt)
|
||||||
print(msg_txt)
|
print(msg_txt)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ def printshow_policies():
|
|||||||
for namespace in namespaces:
|
for namespace in namespaces:
|
||||||
spacing = ' '
|
spacing = ' '
|
||||||
body['policySchemaFilter'] = f'{namespace}.*'
|
body['policySchemaFilter'] = f'{namespace}.*'
|
||||||
|
body['pageToken'] = None
|
||||||
try:
|
try:
|
||||||
policies = gapi.get_all_pages(svc.customers().policies(), 'resolve',
|
policies = gapi.get_all_pages(svc.customers().policies(), 'resolve',
|
||||||
items='resolvedPolicies',
|
items='resolvedPolicies',
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ def sync():
|
|||||||
controlflow.csv_field_error_exit(devicetype_column, input_file.fieldnames)
|
controlflow.csv_field_error_exit(devicetype_column, input_file.fieldnames)
|
||||||
if assettag_column and assettag_column not in input_file.fieldnames:
|
if assettag_column and assettag_column not in input_file.fieldnames:
|
||||||
controlflow.csv_field_error_exit(assettag_column, input_file.fieldnames)
|
controlflow.csv_field_error_exit(assettag_column, input_file.fieldnames)
|
||||||
local_devices = []
|
local_devices = {}
|
||||||
for row in input_file:
|
for row in input_file:
|
||||||
# upper() is very important to comparison since Google
|
# upper() is very important to comparison since Google
|
||||||
# always return uppercase serials
|
# always return uppercase serials
|
||||||
@@ -414,28 +414,43 @@ def sync():
|
|||||||
local_device['deviceType'] = static_devicetype
|
local_device['deviceType'] = static_devicetype
|
||||||
else:
|
else:
|
||||||
local_device['deviceType'] = row[devicetype_column].strip()
|
local_device['deviceType'] = row[devicetype_column].strip()
|
||||||
|
sndt = f"{local_device['serialNumber']}-{local_device['deviceType']}"
|
||||||
if assettag_column:
|
if assettag_column:
|
||||||
local_device['assetTag'] = row[assettag_column].strip()
|
local_device['assetTag'] = row[assettag_column].strip()
|
||||||
local_devices.append(local_device)
|
sndt += f"-{local_device['assetTag']}"
|
||||||
|
local_devices[sndt] = local_device
|
||||||
fileutils.close_file(f)
|
fileutils.close_file(f)
|
||||||
page_message = gapi.got_total_items_msg('Company Devices', '...\n')
|
page_message = gapi.got_total_items_msg('Company Devices', '...\n')
|
||||||
device_fields = ['serialNumber', 'deviceType', 'lastSyncTime', 'name']
|
device_fields = ['serialNumber', 'deviceType', 'lastSyncTime', 'name']
|
||||||
if assettag_column:
|
if assettag_column:
|
||||||
device_fields.append('assetTag')
|
device_fields.append('assetTag')
|
||||||
fields = f'nextPageToken,devices({",".join(device_fields)})'
|
fields = f'nextPageToken,devices({",".join(device_fields)})'
|
||||||
remote_devices = gapi.get_all_pages(ci.devices(), 'list', 'devices',
|
remote_devices = {}
|
||||||
|
remote_device_map = {}
|
||||||
|
result = gapi.get_all_pages(ci.devices(), 'list', 'devices',
|
||||||
customer=customer, page_message=page_message,
|
customer=customer, page_message=page_message,
|
||||||
pageSize=100, filter=device_filter, view='COMPANY_INVENTORY', fields=fields)
|
pageSize=100, filter=device_filter, view='COMPANY_INVENTORY', fields=fields)
|
||||||
remote_device_map = {}
|
for remote_device in result:
|
||||||
for remote_device in remote_devices:
|
|
||||||
sn = remote_device['serialNumber']
|
sn = remote_device['serialNumber']
|
||||||
last_sync = remote_device.pop('lastSyncTime', NEVER_TIME_NOMS)
|
last_sync = remote_device.pop('lastSyncTime', NEVER_TIME_NOMS)
|
||||||
name = remote_device.pop('name')
|
name = remote_device.pop('name')
|
||||||
remote_device_map[sn] = {'name': name}
|
sndt = f"{remote_device['serialNumber']}-{remote_device['deviceType']}"
|
||||||
|
if assettag_column:
|
||||||
|
if 'assetTag' not in remote_device:
|
||||||
|
remote_device['assetTag'] = ''
|
||||||
|
sndt += f"-{remote_device['assetTag']}"
|
||||||
|
remote_devices[sndt] = remote_device
|
||||||
|
remote_device_map[sndt] = {'name': name}
|
||||||
if last_sync == NEVER_TIME_NOMS:
|
if last_sync == NEVER_TIME_NOMS:
|
||||||
remote_device_map[sn]['unassigned'] = True
|
remote_device_map[sndt]['unassigned'] = True
|
||||||
devices_to_add = [device for device in local_devices if device not in remote_devices]
|
devices_to_add = []
|
||||||
missing_devices = [device for device in remote_devices if device not in local_devices]
|
for sndt, device in iter(local_devices.items()):
|
||||||
|
if sndt not in remote_devices:
|
||||||
|
devices_to_add.append(device)
|
||||||
|
missing_devices = []
|
||||||
|
for sndt, device in iter(remote_devices.items()):
|
||||||
|
if sndt not in local_devices:
|
||||||
|
missing_devices.append(device)
|
||||||
print(f'Need to add {len(devices_to_add)} and remove {len(missing_devices)} devices...')
|
print(f'Need to add {len(devices_to_add)} and remove {len(missing_devices)} devices...')
|
||||||
for add_device in devices_to_add:
|
for add_device in devices_to_add:
|
||||||
print(f'Creating {add_device["serialNumber"]}')
|
print(f'Creating {add_device["serialNumber"]}')
|
||||||
@@ -447,8 +462,11 @@ def sync():
|
|||||||
print(f' {add_device["serialNumber"]} already exists')
|
print(f' {add_device["serialNumber"]} already exists')
|
||||||
for missing_device in missing_devices:
|
for missing_device in missing_devices:
|
||||||
sn = missing_device['serialNumber']
|
sn = missing_device['serialNumber']
|
||||||
name = remote_device_map[sn]['name']
|
sndt = f"{sn}-{missing_device['deviceType']}"
|
||||||
unassigned = remote_device_map[sn].get('unassigned')
|
if assettag_column:
|
||||||
|
sndt += f"-{missing_device['assetTag']}"
|
||||||
|
name = remote_device_map[sndt]['name']
|
||||||
|
unassigned = remote_device_map[sndt].get('unassigned')
|
||||||
action = unassigned_missing_action if unassigned else assigned_missing_action
|
action = unassigned_missing_action if unassigned else assigned_missing_action
|
||||||
if action == 'donothing':
|
if action == 'donothing':
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import sys
|
|||||||
import googleapiclient
|
import googleapiclient
|
||||||
|
|
||||||
import gam
|
import gam
|
||||||
from gam.var import *
|
from gam.var import * # pylint: disable=unused-wildcard-import
|
||||||
from gam import controlflow
|
from gam import controlflow
|
||||||
from gam import display
|
from gam import display
|
||||||
from gam import gapi
|
from gam import gapi
|
||||||
@@ -76,6 +76,7 @@ def info():
|
|||||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||||
group = gam.normalizeEmailAddressOrUID(sys.argv[3])
|
group = gam.normalizeEmailAddressOrUID(sys.argv[3])
|
||||||
getUsers = True
|
getUsers = True
|
||||||
|
getSecuritySettings = True
|
||||||
showJoinDate = True
|
showJoinDate = True
|
||||||
showUpdateDate = False
|
showUpdateDate = False
|
||||||
showMemberTree = False
|
showMemberTree = False
|
||||||
@@ -94,11 +95,20 @@ def info():
|
|||||||
elif myarg == 'membertree':
|
elif myarg == 'membertree':
|
||||||
showMemberTree = True
|
showMemberTree = True
|
||||||
i += 1
|
i += 1
|
||||||
|
elif myarg in ['nosecurity', 'nosecuritysettings']:
|
||||||
|
getSecuritySettings = False
|
||||||
else:
|
else:
|
||||||
controlflow.invalid_argument_exit(myarg, 'gam info cigroup')
|
controlflow.invalid_argument_exit(myarg, 'gam info cigroup')
|
||||||
name = group_email_to_id(ci, group)
|
name = group_email_to_id(ci, group)
|
||||||
basic_info = gapi.call(ci.groups(), 'get', name=name)
|
basic_info = gapi.call(ci.groups(), 'get', name=name)
|
||||||
display.print_json(basic_info)
|
display.print_json(basic_info)
|
||||||
|
if getSecuritySettings:
|
||||||
|
sec_info = gapi.call(ci.groups(),
|
||||||
|
'getSecuritySettings',
|
||||||
|
name=f'{name}/securitySettings',
|
||||||
|
readMask='*')
|
||||||
|
print(' Security settings:')
|
||||||
|
display.print_json(sec_info, spacing=' ')
|
||||||
if getUsers and not showMemberTree:
|
if getUsers and not showMemberTree:
|
||||||
if not showJoinDate and not showUpdateDate:
|
if not showJoinDate and not showUpdateDate:
|
||||||
view = 'BASIC'
|
view = 'BASIC'
|
||||||
@@ -116,7 +126,7 @@ def info():
|
|||||||
print(' Members:')
|
print(' Members:')
|
||||||
for member in members:
|
for member in members:
|
||||||
role = get_single_role(member.get('roles', [])).lower()
|
role = get_single_role(member.get('roles', [])).lower()
|
||||||
email = member.get('memberKey', {}).get('id')
|
email = member.get('preferredMemberKey', {}).get('id')
|
||||||
member_type = member.get('type', 'USER').lower()
|
member_type = member.get('type', 'USER').lower()
|
||||||
jc_string = ''
|
jc_string = ''
|
||||||
if showJoinDate:
|
if showJoinDate:
|
||||||
@@ -145,7 +155,7 @@ def print_member_tree(ci, group_id, cached_group_members, spaces, show_role):
|
|||||||
for member in cached_group_members[group_id]:
|
for member in cached_group_members[group_id]:
|
||||||
member_id = member.get('name', '')
|
member_id = member.get('name', '')
|
||||||
member_id = member_id.split('/')[-1]
|
member_id = member_id.split('/')[-1]
|
||||||
email = member.get('memberKey', {}).get('id')
|
email = member.get('preferredMemberKey', {}).get('id')
|
||||||
member_type = member.get('type', 'USER').lower()
|
member_type = member.get('type', 'USER').lower()
|
||||||
if show_role:
|
if show_role:
|
||||||
role = get_single_role(member.get('roles', [])).lower()
|
role = get_single_role(member.get('roles', [])).lower()
|
||||||
@@ -189,7 +199,13 @@ GROUP_ROLES_MAP = {
|
|||||||
def print_():
|
def print_():
|
||||||
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
ci = gapi_cloudidentity.build('cloudidentity_beta')
|
||||||
i = 3
|
i = 3
|
||||||
members = membersCountOnly = managers = managersCountOnly = owners = ownersCountOnly = False
|
members = False
|
||||||
|
membersCountOnly = False
|
||||||
|
managers = False
|
||||||
|
managersCountOnly = False
|
||||||
|
owners = False
|
||||||
|
ownersCountOnly = False
|
||||||
|
memberRestrictions = False
|
||||||
gapi_directory_customer.setTrueCustomerId()
|
gapi_directory_customer.setTrueCustomerId()
|
||||||
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
|
||||||
usemember = None
|
usemember = None
|
||||||
@@ -232,6 +248,15 @@ def print_():
|
|||||||
if myarg == 'managerscount':
|
if myarg == 'managerscount':
|
||||||
managersCountOnly = True
|
managersCountOnly = True
|
||||||
i += 1
|
i += 1
|
||||||
|
elif myarg in ['memberrestrictions']:
|
||||||
|
memberRestrictions = True
|
||||||
|
display.add_titles_to_csv_file(
|
||||||
|
['memberRestrictionQuery',],
|
||||||
|
titles)
|
||||||
|
display.add_titles_to_csv_file(
|
||||||
|
['memberRestrictionEvaluation',],
|
||||||
|
titles)
|
||||||
|
i += 1
|
||||||
else:
|
else:
|
||||||
controlflow.invalid_argument_exit(sys.argv[i], 'gam print cigroups')
|
controlflow.invalid_argument_exit(sys.argv[i], 'gam print cigroups')
|
||||||
if roles:
|
if roles:
|
||||||
@@ -315,7 +340,7 @@ def print_():
|
|||||||
'list',
|
'list',
|
||||||
'memberships',
|
'memberships',
|
||||||
page_message=page_message,
|
page_message=page_message,
|
||||||
message_attribute=['memberKey', 'id'],
|
message_attribute=['preferredMemberKey', 'id'],
|
||||||
soft_errors=True,
|
soft_errors=True,
|
||||||
parent=groupKey_id,
|
parent=groupKey_id,
|
||||||
view='BASIC')
|
view='BASIC')
|
||||||
@@ -329,7 +354,7 @@ def print_():
|
|||||||
ownersList = []
|
ownersList = []
|
||||||
ownersCount = 0
|
ownersCount = 0
|
||||||
for member in groupMembers:
|
for member in groupMembers:
|
||||||
member_email = member['memberKey']['id']
|
member_email = member['preferredMemberKey']['id']
|
||||||
role = get_single_role(member.get('roles', []))
|
role = get_single_role(member.get('roles', []))
|
||||||
if not validRoles or role in validRoles:
|
if not validRoles or role in validRoles:
|
||||||
if role == ROLE_MEMBER:
|
if role == ROLE_MEMBER:
|
||||||
@@ -363,6 +388,16 @@ def print_():
|
|||||||
group['OwnersCount'] = ownersCount
|
group['OwnersCount'] = ownersCount
|
||||||
if not ownersCountOnly:
|
if not ownersCountOnly:
|
||||||
group['Owners'] = memberDelimiter.join(ownersList)
|
group['Owners'] = memberDelimiter.join(ownersList)
|
||||||
|
if memberRestrictions:
|
||||||
|
name = f'{groupKey_id}/securitySettings'
|
||||||
|
print(f'Getting member restrictions for {groupEmail} ({i}/{count}')
|
||||||
|
sec_info = gapi.call(ci.groups(),
|
||||||
|
'getSecuritySettings',
|
||||||
|
name=name,
|
||||||
|
readMask='*')
|
||||||
|
if 'memberRestriction' in sec_info:
|
||||||
|
group['memberRestrictionQuery'] = sec_info['memberRestriction'].get('query', '')
|
||||||
|
group['memberRestrictionEvaluation'] = sec_info['memberRestriction'].get('evaluation', {}).get('state', '')
|
||||||
csvRows.append(group)
|
csvRows.append(group)
|
||||||
if sortHeaders:
|
if sortHeaders:
|
||||||
display.sort_csv_titles([
|
display.sort_csv_titles([
|
||||||
@@ -479,8 +514,8 @@ def print_members():
|
|||||||
view='FULL',
|
view='FULL',
|
||||||
pageSize=500,
|
pageSize=500,
|
||||||
page_message=page_message,
|
page_message=page_message,
|
||||||
message_attribute=['memberKey', 'id'])
|
message_attribute=['preferredMemberKey', 'id'])
|
||||||
#fields='nextPageToken,memberships(memberKey,roles,createTime,updateTime)')
|
#fields='nextPageToken,memberships(preferredMemberKey,roles,createTime,updateTime)')
|
||||||
if roles:
|
if roles:
|
||||||
group_members = filter_members_to_roles(group_members, roles)
|
group_members = filter_members_to_roles(group_members, roles)
|
||||||
for member in group_members:
|
for member in group_members:
|
||||||
@@ -565,7 +600,7 @@ def update():
|
|||||||
items.append(item)
|
items.append(item)
|
||||||
elif len(users_email) > 0:
|
elif len(users_email) > 0:
|
||||||
body = {
|
body = {
|
||||||
'memberKey': {
|
'preferredMemberKey': {
|
||||||
'id': users_email[0]
|
'id': users_email[0]
|
||||||
},
|
},
|
||||||
'roles': [{
|
'roles': [{
|
||||||
@@ -785,12 +820,12 @@ def update():
|
|||||||
page_message=page_message,
|
page_message=page_message,
|
||||||
throw_reasons=gapi_errors.MEMBERS_THROW_REASONS,
|
throw_reasons=gapi_errors.MEMBERS_THROW_REASONS,
|
||||||
parent=parent,
|
parent=parent,
|
||||||
fields='nextPageToken,memberships(memberKey,roles)')
|
fields='nextPageToken,memberships(preferredMemberKey,roles)')
|
||||||
result = filter_members_to_roles(result, roles)
|
result = filter_members_to_roles(result, roles)
|
||||||
if not result:
|
if not result:
|
||||||
print('Group already has 0 members')
|
print('Group already has 0 members')
|
||||||
return
|
return
|
||||||
users_email = [member['memberKey']['id'] for member in result]
|
users_email = [member['preferredMemberKey']['id'] for member in result]
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
f'Group: {group}, Will remove {len(users_email)} {", ".join(roles).lower()}s.\n'
|
f'Group: {group}, Will remove {len(users_email)} {", ".join(roles).lower()}s.\n'
|
||||||
)
|
)
|
||||||
@@ -808,6 +843,7 @@ def update():
|
|||||||
else:
|
else:
|
||||||
i = 4
|
i = 4
|
||||||
body = {}
|
body = {}
|
||||||
|
sec_body = {}
|
||||||
while i < len(sys.argv):
|
while i < len(sys.argv):
|
||||||
myarg = sys.argv[i].lower().replace('_', '')
|
myarg = sys.argv[i].lower().replace('_', '')
|
||||||
if myarg == 'name':
|
if myarg == 'name':
|
||||||
@@ -830,9 +866,21 @@ def update():
|
|||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
i += 2
|
i += 2
|
||||||
|
elif myarg in ['memberrestriction', 'memberrestrictions']:
|
||||||
|
query = sys.argv[i + 1]
|
||||||
|
member_types = {
|
||||||
|
'USER': '1',
|
||||||
|
'SERVICE_ACCOUNT': '2',
|
||||||
|
'GROUP': '3',
|
||||||
|
}
|
||||||
|
for key, val in member_types.items():
|
||||||
|
query = query.replace(key, val)
|
||||||
|
sec_body['memberRestriction'] = {'query': query}
|
||||||
|
i += 2
|
||||||
else:
|
else:
|
||||||
controlflow.invalid_argument_exit(sys.argv[i],
|
controlflow.invalid_argument_exit(sys.argv[i],
|
||||||
'gam update cigroup')
|
'gam update cigroup')
|
||||||
|
if body:
|
||||||
updateMask = ','.join(body.keys())
|
updateMask = ','.join(body.keys())
|
||||||
name = group_email_to_id(ci, group)
|
name = group_email_to_id(ci, group)
|
||||||
print(f'Updating group {group}')
|
print(f'Updating group {group}')
|
||||||
@@ -841,6 +889,18 @@ def update():
|
|||||||
updateMask=updateMask,
|
updateMask=updateMask,
|
||||||
name=name,
|
name=name,
|
||||||
body=body)
|
body=body)
|
||||||
|
if sec_body:
|
||||||
|
updateMask = 'member_restriction.query'
|
||||||
|
# it seems like a bug that API requires /securitySettings
|
||||||
|
# appended to name. We'll see if Google servers change this
|
||||||
|
# at some point.
|
||||||
|
name = f'{group_email_to_id(ci, group)}/securitySettings'
|
||||||
|
print(f'Updating group {group} security settings')
|
||||||
|
gapi.call(ci.groups(),
|
||||||
|
'updateSecuritySettings',
|
||||||
|
name=name,
|
||||||
|
updateMask=updateMask,
|
||||||
|
body=sec_body)
|
||||||
|
|
||||||
|
|
||||||
def group_email_to_id(ci, group, i=0, count=0):
|
def group_email_to_id(ci, group, i=0, count=0):
|
||||||
|
|||||||
@@ -266,6 +266,8 @@ GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP = {
|
|||||||
'customReplyTo',
|
'customReplyTo',
|
||||||
'defaultmessagedenynotificationtext':
|
'defaultmessagedenynotificationtext':
|
||||||
'defaultMessageDenyNotificationText',
|
'defaultMessageDenyNotificationText',
|
||||||
|
'defaultsender':
|
||||||
|
'defaultSender',
|
||||||
'enablecollaborativeinbox':
|
'enablecollaborativeinbox':
|
||||||
'enableCollaborativeInbox',
|
'enableCollaborativeInbox',
|
||||||
'favoriterepliesontop':
|
'favoriterepliesontop':
|
||||||
@@ -979,6 +981,9 @@ def update():
|
|||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
f'Group: {group}, Will add {len(to_add)} and remove {len(to_remove)} {role}s.\n'
|
f'Group: {group}, Will add {len(to_add)} and remove {len(to_remove)} {role}s.\n'
|
||||||
)
|
)
|
||||||
|
for user in to_remove:
|
||||||
|
items.append(
|
||||||
|
['gam', 'update', 'group', group, 'remove', user])
|
||||||
for user in to_add:
|
for user in to_add:
|
||||||
item = ['gam', 'update', 'group', group, 'add']
|
item = ['gam', 'update', 'group', group, 'add']
|
||||||
if role:
|
if role:
|
||||||
@@ -987,9 +992,6 @@ def update():
|
|||||||
item.append(delivery)
|
item.append(delivery)
|
||||||
item.append(user)
|
item.append(user)
|
||||||
items.append(item)
|
items.append(item)
|
||||||
for user in to_remove:
|
|
||||||
items.append(
|
|
||||||
['gam', 'update', 'group', group, 'remove', user])
|
|
||||||
elif myarg in ['delete', 'remove']:
|
elif myarg in ['delete', 'remove']:
|
||||||
_, users_email, _ = _getRoleAndUsers()
|
_, users_email, _ = _getRoleAndUsers()
|
||||||
if not exists(cd, group):
|
if not exists(cd, group):
|
||||||
@@ -1219,7 +1221,7 @@ def getGroupAttrValue(myarg, value, gs_object, gs_body, function):
|
|||||||
params) in list(gs_object['schemas']['Groups']['properties'].items()):
|
params) in list(gs_object['schemas']['Groups']['properties'].items()):
|
||||||
if attrib in ['kind', 'etag', 'email']:
|
if attrib in ['kind', 'etag', 'email']:
|
||||||
continue
|
continue
|
||||||
if myarg == attrib.lower():
|
if myarg == attrib.lower().replace('_', ''):
|
||||||
if params['type'] == 'integer':
|
if params['type'] == 'integer':
|
||||||
try:
|
try:
|
||||||
if value[-1:].upper() == 'M':
|
if value[-1:].upper() == 'M':
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ class GapiGroupNotFoundError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GapiInternalServerError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GapiInvalidError(Exception):
|
class GapiInvalidError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -125,6 +129,7 @@ class ErrorReason(Enum):
|
|||||||
GATEWAY_TIMEOUT = 'gatewayTimeout'
|
GATEWAY_TIMEOUT = 'gatewayTimeout'
|
||||||
GROUP_NOT_FOUND = 'groupNotFound'
|
GROUP_NOT_FOUND = 'groupNotFound'
|
||||||
INTERNAL_ERROR = 'internalError'
|
INTERNAL_ERROR = 'internalError'
|
||||||
|
INTERNAL_SERVER_ERROR = 'internalServerError'
|
||||||
INVALID = 'invalid'
|
INVALID = 'invalid'
|
||||||
INVALID_ARGUMENT = 'invalidArgument'
|
INVALID_ARGUMENT = 'invalidArgument'
|
||||||
INVALID_MEMBER = 'invalidMember'
|
INVALID_MEMBER = 'invalidMember'
|
||||||
@@ -199,6 +204,8 @@ ERROR_REASON_TO_EXCEPTION = {
|
|||||||
GapiGatewayTimeoutError,
|
GapiGatewayTimeoutError,
|
||||||
ErrorReason.GROUP_NOT_FOUND:
|
ErrorReason.GROUP_NOT_FOUND:
|
||||||
GapiGroupNotFoundError,
|
GapiGroupNotFoundError,
|
||||||
|
ErrorReason.INTERNAL_SERVER_ERROR:
|
||||||
|
GapiInternalServerError,
|
||||||
ErrorReason.INVALID:
|
ErrorReason.INVALID:
|
||||||
GapiInvalidError,
|
GapiInvalidError,
|
||||||
ErrorReason.INVALID_ARGUMENT:
|
ErrorReason.INVALID_ARGUMENT:
|
||||||
@@ -336,6 +343,10 @@ def get_gapi_error_detail(e,
|
|||||||
if 'Requested entity was not found' in message or 'does not exist' in message:
|
if 'Requested entity was not found' in message or 'does not exist' in message:
|
||||||
error = _create_http_error_dict(404, ErrorReason.NOT_FOUND.value,
|
error = _create_http_error_dict(404, ErrorReason.NOT_FOUND.value,
|
||||||
message)
|
message)
|
||||||
|
elif http_status == 500:
|
||||||
|
if 'Failed to convert server response to JSON' in message:
|
||||||
|
error = _create_http_error_dict(500, ErrorReason.INTERNAL_SERVER_ERROR.value,
|
||||||
|
message)
|
||||||
else:
|
else:
|
||||||
if 'error_description' in error:
|
if 'error_description' in error:
|
||||||
if error['error_description'] == 'Invalid Value':
|
if error['error_description'] == 'Invalid Value':
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import platform
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
|
GAM_AUTHOR = 'Jay Lee <jay0lee@gmail.com>'
|
||||||
GAM_VERSION = '6.07'
|
GAM_VERSION = '6.09'
|
||||||
GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||||
|
|
||||||
GAM_URL = 'https://git.io/gam'
|
GAM_URL = 'https://git.io/gam'
|
||||||
@@ -124,7 +124,7 @@ SKUS = {
|
|||||||
'Google-Apps': {
|
'Google-Apps': {
|
||||||
'product': 'Google-Apps',
|
'product': 'Google-Apps',
|
||||||
'aliases': ['standard', 'free'],
|
'aliases': ['standard', 'free'],
|
||||||
'displayName': 'G Suite Free/Standard'
|
'displayName': 'G Suite Legacy'
|
||||||
},
|
},
|
||||||
'Google-Apps-For-Business': {
|
'Google-Apps-For-Business': {
|
||||||
'product': 'Google-Apps',
|
'product': 'Google-Apps',
|
||||||
@@ -500,6 +500,7 @@ DRIVEFILE_FIELDS_CHOICES_MAP = {
|
|||||||
'sharedwithmedate': 'sharedWithMeDate',
|
'sharedwithmedate': 'sharedWithMeDate',
|
||||||
'sharedwithmetime': 'sharedWithMeDate',
|
'sharedwithmetime': 'sharedWithMeDate',
|
||||||
'sharinguser': 'sharingUser',
|
'sharinguser': 'sharingUser',
|
||||||
|
'shortcutdetails': 'shortcutDetails',
|
||||||
'spaces': 'spaces',
|
'spaces': 'spaces',
|
||||||
'thumbnaillink': 'thumbnailLink',
|
'thumbnaillink': 'thumbnailLink',
|
||||||
'title': 'title',
|
'title': 'title',
|
||||||
@@ -616,6 +617,7 @@ GOOGLEDOC_VALID_EXTENSIONS_MAP = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MACOS_CODENAMES = {
|
MACOS_CODENAMES = {
|
||||||
|
10: {
|
||||||
6: 'Snow Leopard',
|
6: 'Snow Leopard',
|
||||||
7: 'Lion',
|
7: 'Lion',
|
||||||
8: 'Mountain Lion',
|
8: 'Mountain Lion',
|
||||||
@@ -625,8 +627,11 @@ MACOS_CODENAMES = {
|
|||||||
12: 'Sierra',
|
12: 'Sierra',
|
||||||
13: 'High Sierra',
|
13: 'High Sierra',
|
||||||
14: 'Mojave',
|
14: 'Mojave',
|
||||||
15: 'Catalina'
|
15: 'Catalina',
|
||||||
}
|
16: 'Big Sur'
|
||||||
|
},
|
||||||
|
11: 'Big Sur',
|
||||||
|
}
|
||||||
|
|
||||||
_MICROSOFT_FORMATS_LIST = [{
|
_MICROSOFT_FORMATS_LIST = [{
|
||||||
'mime':
|
'mime':
|
||||||
@@ -891,7 +896,6 @@ RT_TAG_REPLACE_PATTERN = re.compile(r'{(.*?)}')
|
|||||||
LOWERNUMERIC_CHARS = string.ascii_lowercase + string.digits
|
LOWERNUMERIC_CHARS = string.ascii_lowercase + string.digits
|
||||||
ALPHANUMERIC_CHARS = LOWERNUMERIC_CHARS + string.ascii_uppercase
|
ALPHANUMERIC_CHARS = LOWERNUMERIC_CHARS + string.ascii_uppercase
|
||||||
URL_SAFE_CHARS = ALPHANUMERIC_CHARS + '-._~'
|
URL_SAFE_CHARS = ALPHANUMERIC_CHARS + '-._~'
|
||||||
FILENAME_SAFE_CHARS = ALPHANUMERIC_CHARS + '-_.() '
|
|
||||||
|
|
||||||
FILTER_ADD_LABEL_TO_ARGUMENT_MAP = {
|
FILTER_ADD_LABEL_TO_ARGUMENT_MAP = {
|
||||||
'IMPORTANT': 'important',
|
'IMPORTANT': 'important',
|
||||||
@@ -1106,7 +1110,8 @@ GROUP_SETTINGS_LIST_ATTRIBUTES = set([
|
|||||||
'whoCanUnmarkFavoriteReplyOnAnyTopic',
|
'whoCanUnmarkFavoriteReplyOnAnyTopic',
|
||||||
'whoCanViewGroup',
|
'whoCanViewGroup',
|
||||||
'whoCanViewMembership',
|
'whoCanViewMembership',
|
||||||
# Miscellaneous hoices
|
# Miscellaneous choices
|
||||||
|
'default_sender',
|
||||||
'messageModerationLevel',
|
'messageModerationLevel',
|
||||||
'replyTo',
|
'replyTo',
|
||||||
'spamModerationLevel',
|
'spamModerationLevel',
|
||||||
@@ -1241,10 +1246,12 @@ GC_DOMAIN = 'domain'
|
|||||||
GC_DRIVE_DIR = 'drive_dir'
|
GC_DRIVE_DIR = 'drive_dir'
|
||||||
# Enable Delegated Admin Service Accounts
|
# Enable Delegated Admin Service Accounts
|
||||||
GC_ENABLE_DASA = 'enabledasa'
|
GC_ENABLE_DASA = 'enabledasa'
|
||||||
# If no_browser is False, writeCSVfile won't open a browser when todrive is set
|
# If no_browser is True, writeCSVfile won't open a browser when todrive is set
|
||||||
# and doRequestOAuth prints a link and waits for the verification code when
|
# and doRequestOAuth prints a link and waits for the verification code when
|
||||||
# oauth2.txt is being created
|
# oauth2.txt is being created
|
||||||
GC_NO_BROWSER = 'no_browser'
|
GC_NO_BROWSER = 'no_browser'
|
||||||
|
# If no_tdemail is True, writeCSVfile won't send an email
|
||||||
|
GC_NO_TDEMAIL = 'no_tdemail'
|
||||||
# oauth_browser forces usage of web server OAuth flow that proved problematic.
|
# oauth_browser forces usage of web server OAuth flow that proved problematic.
|
||||||
GC_OAUTH_BROWSER = 'oauth_browser'
|
GC_OAUTH_BROWSER = 'oauth_browser'
|
||||||
# Disable GAM API caching
|
# Disable GAM API caching
|
||||||
@@ -1299,6 +1306,7 @@ GC_Defaults = {
|
|||||||
GC_DRIVE_DIR: '',
|
GC_DRIVE_DIR: '',
|
||||||
GC_ENABLE_DASA: False,
|
GC_ENABLE_DASA: False,
|
||||||
GC_NO_BROWSER: False,
|
GC_NO_BROWSER: False,
|
||||||
|
GC_NO_TDEMAIL: False,
|
||||||
GC_NO_CACHE: False,
|
GC_NO_CACHE: False,
|
||||||
GC_NO_SHORT_URLS: False,
|
GC_NO_SHORT_URLS: False,
|
||||||
GC_NO_UPDATE_CHECK: False,
|
GC_NO_UPDATE_CHECK: False,
|
||||||
@@ -1384,6 +1392,9 @@ GC_VAR_INFO = {
|
|||||||
GC_NO_BROWSER: {
|
GC_NO_BROWSER: {
|
||||||
GC_VAR_TYPE: GC_TYPE_BOOLEAN
|
GC_VAR_TYPE: GC_TYPE_BOOLEAN
|
||||||
},
|
},
|
||||||
|
GC_NO_TDEMAIL: {
|
||||||
|
GC_VAR_TYPE: GC_TYPE_BOOLEAN
|
||||||
|
},
|
||||||
GC_NO_CACHE: {
|
GC_NO_CACHE: {
|
||||||
GC_VAR_TYPE: GC_TYPE_BOOLEAN
|
GC_VAR_TYPE: GC_TYPE_BOOLEAN
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ importlib.metadata; python_version < '3.8'
|
|||||||
passlib>=1.7.2
|
passlib>=1.7.2
|
||||||
python-dateutil
|
python-dateutil
|
||||||
yubikey-manager>=4.0.0
|
yubikey-manager>=4.0.0
|
||||||
|
pathvalidate
|
||||||
|
|||||||
49
src/setup.cfg
Normal file
49
src/setup.cfg
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
[metadata]
|
||||||
|
name = GAM for Google Workspace
|
||||||
|
version = 6.0.7
|
||||||
|
description = Command line management for Google Workspaces
|
||||||
|
long_description = file: readme.md
|
||||||
|
long_description_content_type = text/markdown
|
||||||
|
url = https://github.com/jay0lee/GAM
|
||||||
|
author = Jay Lee
|
||||||
|
author_email = jay0lee@gmail.com
|
||||||
|
license = Apache
|
||||||
|
license_files = LICENSE
|
||||||
|
keywords = google, oauth2, gsuite, google-apps, google-admin-sdk, google-drive, google-cloud, google-calendar, gam, google-api, oauth2-client, google-workspace
|
||||||
|
classifiers =
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3 :: Only
|
||||||
|
Programming Language :: Python :: 3.6
|
||||||
|
Programming Language :: Python :: 3.7
|
||||||
|
Programming Language :: Python :: 3.8
|
||||||
|
Programming Language :: Python :: 3.9
|
||||||
|
License :: OSI Approved :: Apache License
|
||||||
|
|
||||||
|
[options]
|
||||||
|
packages = find:
|
||||||
|
python_requires = >=3.6
|
||||||
|
install_requires =
|
||||||
|
cryptography
|
||||||
|
distro; sys_platform == 'linux'
|
||||||
|
filelock
|
||||||
|
google-api-python-client >= 2.1
|
||||||
|
google-auth-httplib2
|
||||||
|
google-auth-oauthlib >= 0.4.1
|
||||||
|
google-auth >= 1.11.2
|
||||||
|
httplib2 >= 0.17.0
|
||||||
|
importlib.metadata; python_version < '3.8'
|
||||||
|
passlib >= 1.7.2
|
||||||
|
python-dateutil
|
||||||
|
yubikey-manager >= 4.0.0
|
||||||
|
pathvalidate
|
||||||
|
|
||||||
|
# used during pip install .[test]
|
||||||
|
[options.extras_require]
|
||||||
|
test = pre-commit
|
||||||
|
|
||||||
|
[options.entry_points]
|
||||||
|
console_scripts =
|
||||||
|
gam = gam.__main__:main
|
||||||
|
|
||||||
|
[bdist_wheel]
|
||||||
|
universal = True
|
||||||
3
src/setup.py
Normal file
3
src/setup.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
setup()
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
#from packaging import version
|
from packaging import version
|
||||||
from distutils.version import LooseVersion
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
a = sys.argv[1]
|
a = sys.argv[1]
|
||||||
b = sys.argv[2]
|
b = sys.argv[2]
|
||||||
#result = version.parse(a) >= version.parse(b)
|
result = version.parse(a) >= version.parse(b)
|
||||||
result = LooseVersion(a) >= LooseVersion(b)
|
|
||||||
if result:
|
if result:
|
||||||
print('OK: %s is equal or newer than %s' % (a, b))
|
print('OK: %s is equal or newer than %s' % (a, b))
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user