Compare commits

...

84 Commits
v4.91 ... v4.95

Author SHA1 Message Date
Jay Lee
93916d4ed1 remove timeout that caused sporadic failure 2019-10-01 12:44:22 -04:00
Jay Lee
1d1e48acb7 One lock for r/w, cleanup .lock file. Fixes #1011.
Keep lock in place thru read and possible write of oauth2.txt. This
allows only a single process to refresh credentials, others won't see the token
until post-refresh and we avoid multiple refreshes in parallel.

filelock can cleanup after itself on Windows but has to avoid a deadlock on *nix.
Try to cleanup the .lock file for it.
2019-10-01 10:59:46 -04:00
Jay Lee
1884e1a111 file locking for oauth2.txt 2019-09-30 16:18:24 -04:00
Jay Lee
2d0396da21 GAM 4.95, Ross' changes in #1018, re-enable older Python tests 2019-09-28 12:15:31 -04:00
Jay Lee
cce47ba723 another attempt to fix bionic 2019-09-28 07:00:34 -04:00
Jay Lee
2831680d14 stop building MacOS 10.1[01] 2019-09-28 06:42:44 -04:00
Jay Lee
5e3374acbc unset LD_LIBRARY_PATH to fix deploy 2019-09-28 06:34:24 -04:00
Jay Lee
13d7de9501 travis 2019-09-27 22:43:14 -04:00
Jay Lee
d78abc92f2 update ruby gems 2019-09-27 21:32:16 -04:00
Jay Lee
07672eb874 use newer ruby on github 2019-09-27 21:12:15 -04:00
Jay Lee
e7225ce487 travis 2019-09-27 19:56:13 -04:00
Jay Lee
cb24d3bf78 travis 2019-09-27 19:52:37 -04:00
Jay Lee
8360019080 parallel tests 2019-09-27 18:59:52 -04:00
Jay Lee
f3b34bea26 travis 2019-09-27 16:50:33 -04:00
Jay Lee
edf09a2d7b travis 2019-09-27 16:14:47 -04:00
Jay Lee
1de445c5b8 travis 2019-09-27 15:28:31 -04:00
Jay Lee
8d7f307173 more travis 2019-09-27 14:59:46 -04:00
Jay Lee
0a23a6d084 more travis 2019-09-27 14:37:59 -04:00
Jay Lee
87fa70be2c travis 2019-09-27 13:40:41 -04:00
Jay Lee
b57c62fe1b mroe travis 2019-09-27 12:34:43 -04:00
Jay Lee
a8c1051e0f more travis work 2019-09-27 11:43:27 -04:00
Jay Lee
d7ba12e729 flush travis cache, osx libraries 2019-09-27 10:17:23 -04:00
Jay Lee
1d3c47f3fd more diag 2019-09-18 18:52:26 -04:00
Jay Lee
4ae81bae99 brew upgrade on MacOS 2019-09-18 18:19:19 -04:00
Jay Lee
2fc301d061 full path to win python 2019-09-18 18:14:58 -04:00
Jay Lee
fc6e6d1ab6 verbose logging on MacOS 2019-09-18 17:50:30 -04:00
Jay Lee
9cb4ee9d6f figure out what's up with path 2019-09-18 17:44:37 -04:00
Jay Lee
0e1da6982b call python 2019-09-18 16:40:21 -04:00
Jay Lee
28573b47a8 more build fixes 2019-09-18 16:35:28 -04:00
Jay Lee
851bd1ef14 not gam 2019-09-18 15:47:25 -04:00
Jay Lee
0c7d64563d missing $ for bash var 2019-09-18 15:35:29 -04:00
Jay Lee
b984f62bbf a_atleast_b tool 2019-09-18 15:16:16 -04:00
Jay Lee
a89e0936c2 OpenSSL 1.1.1d on Windows 2019-09-18 14:59:33 -04:00
Jay Lee
677146d905 OpenSSL 1.1.1d 2019-09-18 14:11:15 -04:00
Ross Scroggs
00b7ead8bb Standard Got messages to always show total_items (#1015) 2019-09-18 08:40:57 -04:00
Ross Scroggs
dce5016261 Add recoveryEmail/Phone to users field list (#1012) 2019-09-13 10:33:03 -04:00
Ross Scroggs
2bc759778c Keep pylint happy (#1008) 2019-09-12 18:49:46 -04:00
Jay Lee
3aa6869a4b fix MacOS vars 2019-09-06 11:21:39 -04:00
Jay Lee
209fdfd5b9 Merge branch 'master' of https://github.com/jay0lee/GAM 2019-09-06 11:15:27 -04:00
Jay Lee
eec0df14b5 MacOS codenames 2019-09-06 11:14:03 -04:00
Ross Scroggs
7e2810d33d Fix code to avoid trap when description is long (#1004)
* Fix code to avoid trap when description is long

* Code cleanup
2019-09-06 11:12:42 -04:00
Jay Lee
78404c8cd3 cleanup MacOS 2019-09-06 10:56:22 -04:00
Jay Lee
f05ceecf8e no x86 on GitHub Actions :-/ 2019-09-06 10:45:41 -04:00
Jay Lee
bcef526213 More recognizable OS info in gam version 2019-09-06 10:44:06 -04:00
Jay Lee
00d5767246 show Python MacOS versions 2019-09-06 07:17:47 -04:00
Jay Lee
6655301bfe Merge branch 'master' of https://github.com/jay0lee/GAM 2019-09-05 19:52:08 -04:00
Jay Lee
5beff97f95 test building on multiple MacOS versions 2019-09-05 19:51:46 -04:00
Jay Lee
cfa6b49bab Update pythonpackage.yml 2019-09-05 14:42:00 -04:00
Jay Lee
a659d5fada Update pythonpackage.yml 2019-09-05 13:54:05 -04:00
Jay Lee
c1521bfa3f Update pythonpackage.yml 2019-09-05 13:47:27 -04:00
Jay Lee
096e6c911a Update pythonpackage.yml 2019-09-05 13:02:54 -04:00
Jay Lee
26f7cd38e5 add runs-on 2019-09-05 12:56:12 -04:00
Jay Lee
802541c09f Test run of GitHub Actions 2019-09-05 12:52:42 -04:00
Jay Lee
cfd36c2836 GAM 4.94, pull in Ross changes in #1003 2019-08-30 11:45:52 -04:00
Jay Lee
7689ac7bed disable ssl verify on time sync so we know when clock is WAY off 2019-08-30 11:15:56 -04:00
Jay Lee
7a5ba99b36 Even better SA check 2019-08-30 11:02:42 -04:00
Ross Scroggs
8f4a40bc9a Add timeoffset option to gam version (#1002)
* Add timeoffset option to gam version

* Update timeOffset checking
2019-08-29 14:32:45 -04:00
Jay Lee
e3ab846d70 update user passwords 2019-08-27 14:28:04 -04:00
Jay Lee
29db574bc5 Merge branch 'master' of https://github.com/jay0lee/GAM 2019-08-27 10:19:48 -04:00
Jay Lee
4851d5b62f upgrade MUSL 2019-08-27 10:19:33 -04:00
Jay Lee
caef16bdee add email scope to SA, check serviceaccount verifies proper DwD and scopes for token 2019-08-27 10:18:36 -04:00
Ross Scroggs
1a0f9ab66a handle empty recoveryPhone (#999)
* handle empty recoveryPhone

* Test empty recoveryemail/recoveryphone

* Handle autoUpdateExpiration for CrOS
2019-08-21 15:03:05 -04:00
Ross Scroggs
021c3bfb13 Handle more errors with short URL oauth create (#998) 2019-08-18 17:34:55 -04:00
Jay Lee
b3dfa41df6 GAM user agent on short URL 2019-08-15 18:21:54 -04:00
Jay Lee
5f94263db2 Use _createHttpObj() for short URL 2019-08-15 18:15:56 -04:00
Jay Lee
68d8e46b4c force pip upgrades 2019-08-15 14:50:23 -04:00
Jay Lee
ed221b0d7b just use static recovery phone 2019-08-15 13:31:26 -04:00
Jay Lee
1243563cd4 try to make MacOS tr happy 2019-08-15 12:38:47 -04:00
Jay Lee
1170457a39 GAM 4.93, remove *MAX_RESULTS config options 2019-08-15 12:35:10 -04:00
Jay Lee
435ed9f568 use area code 212 always 2019-08-15 12:18:36 -04:00
Jay Lee
81884e48d0 Support recovery email/phone for users 2019-08-15 12:00:36 -04:00
Jay Lee
a7be6d233b confirm API call supports maxResults before checking value 2019-08-15 10:08:03 -04:00
Jay Lee
584ddba1a5 Dynamic maxResults from discovery or exception 2019-08-14 16:11:14 -04:00
Jay Lee
2bc6c8bca0 200 or bust for URL shorten 2019-08-13 08:35:06 -04:00
Jay Lee
fc1e81a01d oauthbrowser.txt, GAM 4.92 2019-08-12 14:02:59 -04:00
Jay Lee
eebfaaf373 finish pyinstaller install 2019-08-12 12:48:01 -04:00
Jay Lee
652223d9bc Pyinstaller windows 64 bit bootloader compile 2019-08-12 12:29:51 -04:00
Jay Lee
e75664fd2e roll Python 3.5 back to Xenial 2019-08-12 12:08:09 -04:00
Jay Lee
556278b216 Use bionic for Python source tests 2019-08-12 11:57:24 -04:00
Jay Lee
f9bfaa98bb fix cros/mobile print 2019-08-12 11:47:44 -04:00
Jay Lee
b6bd2da6ce Short OAuth URLs, make console flow default to reduce issues 2019-08-12 11:00:26 -04:00
Jay Lee
7c36a6b601 Merge branch 'master' of https://github.com/jay0lee/GAM 2019-08-12 10:41:22 -04:00
Ross Scroggs
413924b11a Generalize expression to find group settings values (#994) 2019-08-12 10:41:17 -04:00
Jay Lee
251883dae5 add cros/mobile print tests 2019-08-10 15:46:52 -04:00
13 changed files with 549 additions and 253 deletions

24
.github/workflows/pythonpackage.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: GAM
on: [push]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-18.04, ubuntu-16.04, windows-2019, windows-2016, macOS-10.14]
python-version: [3.5, 3.6, 3.7]
steps:
- uses: actions/checkout@v1
- name: Set up $(( matrix.runs-on )) Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r src/requirements.txt
- name: Run GAM
run: |
python src/gam.py version extended

View File

@@ -3,9 +3,8 @@ if: tag IS blank
env:
global:
- BUILD_PYTHON_VERSION=3.7.4
- BUILD_OPENSSL_VERSION=1.1.1c
- BUILD_OPENSSL_VERSION=1.1.1d
- PATCHELF_VERSION=0.9
- MUSL_VERSION=1.1.22
- PYINSTALLER_VERSION=3.5
- secure: "FSKvLaiqhKz21SVgAQZI3bSX34Ffyev4l+R2G//QXNDu6UVQcuFsykzw+eZEG7fkhotXr8BMDL7xIkookiL8eLwUtcd/Z95HCjPBBHcmCSQleyvuuJBxdrQ9xldmiGLzMCYiumSH9OH4uJhQ39Yjnjsa8TK+PlTci6a/BTzlYyBSyDYDf7Iv/uhfQPDHL3pNwrQPHf4fL6/jcvo+uaPcv83AVZkNzZjjyoi9Aa+uh9xlbyHg11jp44463qqxoxTdYik3pYuXRBPjknjOGcnFHqn+QOVSdRQoiwbmT8xVuYuCzTv9THhuJ//i5u7s4y3Xyl7u17B3tdm86UlMpQHy/w9EsYaSBPOU4oPNomRtOnTSugh0v9ZBwptP5XfbslII/iA+LQdzTHhchn0W0CRyDqjOMSestWlrsq5NZJtBJTYHbebllOhEI7xbj9tY+re1zFWSPMOPgHJP23ovsdk3hD9OT93AzRHInCx5IxL6QvEgRhAancRuGkf2rGP0g/vX9fQ0Il3rNMSQxHB5CyHUBtUJ9nhU79YkMDZicD0jFMEwjWJO3itAp3ynoLXRgktgQCYUfgc9SpdWKD5SXLCYnSo22JD3D1P6h2EertRHaoKRLb+CRXQC/lM8uh/W+BjA2Xe6Vut2I/72ndjM+10T7E2xk1CFyCH37a5p8cH26Fs="
- secure: "J9380tGLOZWa7dSH1y5Il8T5JQpN6ad81gI6VR1HIU0svpRdjgikyDA7ca2MKYDUYYY9yVSkTV6gCl6iIU/9+SKaYugpP+tkvdGYkC2moJdcTgYM/WOnIK9ExQ3BPhN1neGxJjPTwKo1ft27mtZ2I5vuCiBwIcnKWLnKPyW3PD+mWpfqiLuEzkHoAh6G3jC4qbcCrZDeX/knE+PzqESUEi+8k1G8gYcSDWujba9ypSsqZ8T/MXagGla6l7y2Rz+/KZTJmFHwKAA10V+xPLVqxoiqi4ar66yUqy0BamwRXPcseI+ns3Q+4lUpMqVQ5GlRy7LF1xC8myjmcAexXk0F9hg+CMzewKI8UgmQH/ZJvQZEh8s6mW26+CqA4d3zMQkWaR0WtEtpiuH7AGHCflIqvEQ6UiG7ia3B8iZfW2wl0j/kqx4OuHkS3r0pWKVVIIvCj9Ow2BHP7SpiV1AcUGsVxzwbgTh67fitna3Z3c6Uj8ccQlNr7ZIt1az6Wf3w5njijkLOiBpQSLKunTTCTSge/JzBTKUcie3RE9vzirl58gUxAt36nDtPWnory+RttMZrOkBVbTeSxp+IUe8pNwLFPHABsafXsjkfzBOtFmm+0ZXWt2Rlog5NvlemJfQUWDlsL4g+BSakzN+4sIPKzSauWDHyaEeULY7Uprkil6c5zwo="
@@ -37,14 +36,14 @@ cache:
matrix:
include:
# - os: linux
# name: "Linux 64-bit Bionic"
# dist: bionic
# language: bash
# env:
# - GAMOS=linux
# - PLATFORM=x86_64
# - VMTYPE=cache
- os: linux
name: "Linux 64-bit Bionic"
dist: bionic
language: bash
env:
- GAMOS=linux
- PLATFORM=x86_64
- VMTYPE=build
- os: linux
name: "Linux 64-bit Xenial"
dist: xenial
@@ -70,7 +69,7 @@ matrix:
- PLATFORM=x86_64
- VMTYPE=build
- os: linux
name: "Linux 64-bit Xenial - Python 3.5 Source Testing"
name: "Linux 64-bit - Python 3.5 Source Testing"
dist: xenial
language: python
python:
@@ -80,8 +79,8 @@ matrix:
- PLATFORM=x86_64
- VMTYPE=test
- os: linux
name: "Linux 64-bit Xenial - Python 3.6 Source Testing"
dist: xenial
name: "Linux 64-bit - Python 3.6 Source Testing"
dist: bionic
language: python
python:
- "3.6"
@@ -90,8 +89,8 @@ matrix:
- PLATFORM=x86_64
- VMTYPE=test
- os: linux
name: "Linux 64-bit Xenial - Python 3.8-dev Source Testing"
dist: xenial
name: "Linux 64-bit - Python 3.8-dev Source Testing"
dist: bionic
language: python
python:
- "3.8-dev"
@@ -100,8 +99,8 @@ matrix:
- PLATFORM=x86_64
- VMTYPE=test
- os: linux
name: "Linux 64-bit Xenial - Python nightly Source Testing"
dist: xenial
name: "Linux 64-bit - Python nightly Source Testing"
dist: bionic
language: python
python:
- "nightly"
@@ -110,13 +109,29 @@ matrix:
- PLATFORM=x86_64
- VMTYPE=test
- os: osx
name: "MacOS 64-bit"
name: "MacOS 10.12"
language: generic
osx_image: xcode9.2
env:
- GAMOS=macos
- PLATFORM=x86_64
- VMTYPE=build
- os: osx
name: "MacOS 10.13"
language: generic
osx_image: xcode10.1
env:
- GAMOS=macos
- PLATFORM=x86_64
- VMTYPE=build
- os: osx
name: "MacOS 10.14"
language: generic
osx_image: xcode11
env:
- GAMOS=macos
- PLATFORM=x86_64
- VMTYPE=build
- os: windows
name: "Windows 64-bit"
language: shell
@@ -144,9 +159,9 @@ script:
- $gam version extended
- $gam version | grep travis # travis should be part of the path (not /tmp or such)
# determine which Python version GAM is built with and ensure it's at least build version from above.
- if [ "VMTYPE" == "build" ]; then vline=$(gam version | grep "Python "); python_line=($vline); this_python=${python_line[1]}; tools/a_atleast_b.py $this_python $BUILD_PYTHON_VERSION; fi
- if [ "$VMTYPE" == "build" ]; then vline=$($gam version | grep "Python "); python_line=($vline); this_python=${python_line[1]}; $python tools/a_atleast_b.py $this_python $BUILD_PYTHON_VERSION; fi
# determine which OpenSSL version GAM is built with and ensure it's at least build version from above.
- if [ "VMTYPE" == "build" ]; then vline=$(gam version extended | grep "OpenSSL "); openssl_line=($vline); this_openssl=${openssl_line[1]}; tools/a_atleast_b.py $this_openssl $BUILD_OPENSSL_VERSION; fi
- if [ "$VMTYPE" == "build" ]; then vline=$($gam version extended | grep "OpenSSL "); openssl_line=($vline); this_openssl=${openssl_line[1]}; $python tools/a_atleast_b.py $this_openssl $BUILD_OPENSSL_VERSION; fi
- if [ "$VMTYPE" == "build" ]; then $gam version extended | grep TLSv1\.[23]; fi # Builds should default TLS 1.2 or 1.3 to Google
- if [ "$VMTYPE" == "build" ]; then GAM_TLS_MIN_VERSION=TLSv1_2 $gam version extended location tls-v1-0.badssl.com:1010; [[ $? == 3 ]]; fi # expect fail since server doesn't support our TLS version
- export jid="$(cut -d'.' -f2 <<<"$TRAVIS_JOB_NUMBER")"
@@ -170,13 +185,15 @@ script:
for i in {01..20};
do echo $newbase-bulkuser-$i >> sample.csv;
done; fi
- if [ "$e2e" = true ]; then $gam create user $newuser firstname Travis lastname $jid password random travis.jid $jid; fi
- if [ "$e2e" = true ]; then $gam create user $newuser firstname Travis lastname $jid password random recoveryphone 12125121110 recoveryemail jay0lee@gmail.com travis.jid $jid; fi
- if [ "$e2e" = true ]; then $gam user $gam_user sendemail recipient $newuser subject "test message $newbase" message "Travis test message"; fi
- if [ "$e2e" = true ]; then $gam create group $newgroup name "Travis $jid group" description "This is a description" isarchived true; fi
- if [ "$e2e" = true ]; then $gam user $newuser add license gsuitebusiness; fi
- if [ "$e2e" = true ]; then $gam update group $newgroup add owner $gam_user; fi
- if [ "$e2e" = true ]; then $gam update group $newgroup add member $newuser; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam create user ~~email~~ firstname "Travis Bulk" lastname ~~email~~ travis.jid $jid; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update user ~~email~~ recoveryphone 12125121110 recoveryemail jay0lee@gmail.com password random; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update user ~~email~~ recoveryphone "" recoveryemail ""; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user ~email add license gsuitebusiness; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam user $gam_user sendemail recipient ~~email~~@pdl.jaylee.us subject "test message $newbase" message "Travis test message"; fi
- if [ "$e2e" = true ]; then $gam csv sample.csv gam update group $newgroup add member ~email; fi
@@ -227,9 +244,12 @@ script:
- if [ "$e2e" = true ]; then $gam user $gam_user show tokens; fi
- if [ "$e2e" = true ]; then $gam delete user $newuser; fi
- if [ "$e2e" = true ]; then $gam print users query "travis.jid=$jid" | $gam csv - gam delete user ~primaryEmail; fi
- if [ "$e2e" = true ]; then $gam print mobile; fi
- if [ "$e2e" = true ]; then $gam print cros allfields nolists; fi
before_deploy:
- export TRAVIS_TAG="preview"
- unset LD_LIBRARY_PATH
deploy:
provider: releases

View File

@@ -257,16 +257,20 @@ If an item contains spaces, it should be surrounded by ".
annotatedassetid|assedid|asset|
annotatedlocation|location|
annotateduser|user|
autoupdateexpiration|
bootmode|
cpustatusreports|
devicefiles|
deviceid|
diskvolumereports|
dockmacaddress|
ethernetmacaddress|
ethernetmacaddress0|
firmwareversion|
lastenrollmenttime|
lastsync|
macaddress|
manufacturedate|
meid|
model|
notes|
@@ -299,6 +303,7 @@ If an item contains spaces, it should be surrounded by ".
cancomment|
canreadrevisions|
copyable|
copyrequireswriterpermission|
createddate|createdtime|
description|
editable|
@@ -521,6 +526,8 @@ If an item contains spaces, it should be surrounded by ".
phones|phone|
posixaccounts|posix|
primaryemail|username|
recoveryemail|
recoveryphone|
relations|relation|
ssh|sshkeys|sshpublickeys|
suspended|
@@ -664,12 +671,16 @@ Specify a collection of Users by directly specifying them or by specifiying item
<DriveFileAddAttributes> ::=
(localfile <FileName>)|
(convert)|(ocr)|(ocrlanguage <Language>)|(restricted|restrict)|(starred|star)|(trashed|trash)|(viewed|view)|
(convert)|(ocr)|(ocrlanguage <Language>)|
(restricted|restrict)|(starred|star)|(trashed|trash)|(viewed|view)|
copyrequireswriterpermission|
(lastviewedbyme <Time>)|(modifieddate|modifiedtime <Time>)|(description <String>)|(mimetype <MimeType>)|
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare
<DriveFileUpdateAttributes> ::=
(localfile <FileName>)|
(convert)|(ocr)|(ocrlanguage <Language>)|(restricted|restrict <Boolean>)|(starred|star <Boolean>)|(trashed|trash <Boolean>)|(viewed|view <Boolean>)|
(convert)|(ocr)|(ocrlanguage <Language>)|
(restricted|restrict <Boolean>)|(starred|star <Boolean>)|(trashed|trash <Boolean>)|(viewed|view <Boolean>)|
(copyrequireswriterpermission <Boolean>)|
(lastviewedbyme <Time>)|(modifieddate <Time>)|(description <String>)|(mimetype <MimeType>)|
(parentid <DriveFolderID>)|(parentname <DriveFolderName>)|(anyownerparentname <DriveFolderName>)|writerscantshare
<GroupSettingsAttribute> ::=
@@ -776,6 +787,8 @@ Specify a collection of Users by directly specifying them or by specifiying item
(note clear|([text_html|text_plain] <String>|(file <FileName> [charset <Charset>])))|
(org|ou|orgunitpath <OrgUnitPath>)
(password random|<Password>)|
(recoveryemail <EmailAddress>)|
(recoveryphone <string>)|
(suspended <Boolean>)|
(<SchemaName>.<FieldName> [multivalued|multivalue|value|multinonempty [type home|other|work|(custom <String>)]] <String>)
<UserMultiAttributes> ::=
@@ -799,7 +812,7 @@ Specify a collection of Users by directly specifying them or by specifiying item
<UserBasicAttribute>|
<UserMultiAttribute>
gam version [check|checkrc|simple|extended] [location <HostName>]
gam version [check|checkrc|simple|extended] [timeoffset] [location <HostName>]
gam help
gam batch <FileName>|- [charset <Charset>]
@@ -1075,6 +1088,7 @@ gam info member <UserItem> <GroupItem>
gam print group-members|groups-members [todrive]
([domain <DomainName>] ([member <UserItem>]|[query <QueryGroup>]))|[group|group_ns|group_susp <GroupItem>] [notsuspended|suspended]
[roles <GroupRoleList>] [membernames] [fields <MembersFieldNameList>]
[includederivedmembership]
gam print licenses [todrive] [(products|product <ProductIDList>)|(skus|sku <SKUIDList>)|allskus|gsuite] [countsonly]
gam show license|licenses|licence|licences [(products|product <ProductIDList>)|(skus|sku <SKUIDList>)|allskus|gsuite]
@@ -1295,7 +1309,7 @@ gam <UserTypeEntity> draftemail [recipient|to <EmailAddress>] [from <EmailAddres
gam <UserTypeEntity> importemail [recipient|to <EmailAddress>] [from <EmailAddress>]
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
[labels <LabelNameList>] (header <String> <String>)*
[deleted] [date <Time>]
[deleted] [date <Time>]
[nevercheckspam] [processforcalendar]
gam <UserTypeEntity> insertemail [recipient|to <EmailAddress>] [from <EmailAddress>]
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]

View File

@@ -28,6 +28,7 @@ import csv
import datetime
import difflib
from email import message_from_string
from filelock import FileLock
import hashlib
import io
import json
@@ -70,6 +71,9 @@ import httplib2
import utils
from var import *
if platform.system() == 'Linux':
import distro
# Finding path method varies between Python source, PyInstaller and StaticX
if os.environ.get('STATICX_PROG_PATH', False):
# StaticX static executable
@@ -106,11 +110,11 @@ google_auth_httplib2.Request.__call__ = _request_with_user_agent(
google_auth_httplib2.AuthorizedHttp.request = _request_with_user_agent(
google_auth_httplib2.AuthorizedHttp.request)
def _createHttpObj(cache=None, override_min_tls=None, override_max_tls=None):
def _createHttpObj(cache=None, timeout=None, override_min_tls=None, override_max_tls=None):
tls_minimum_version = override_min_tls if override_min_tls else GC_Values[GC_TLS_MIN_VERSION]
tls_maximum_version = override_max_tls if override_max_tls else GC_Values[GC_TLS_MAX_VERSION]
return httplib2.Http(ca_certs=GC_Values[GC_CA_FILE], tls_maximum_version=tls_maximum_version, tls_minimum_version=tls_minimum_version,
cache=cache)
cache=cache, timeout=timeout)
def showUsage():
doGAMVersion(checkForArgs=False)
@@ -669,13 +673,8 @@ def SetGlobalVariables():
_getOldEnvVar(GC_CUSTOMER_ID, 'CUSTOMER_ID')
_getOldEnvVar(GC_CHARSET, 'GAM_CHARSET')
_getOldEnvVar(GC_NUM_THREADS, 'GAM_THREADS')
_getOldEnvVar(GC_ACTIVITY_MAX_RESULTS, 'GAM_ACTIVITY_MAX_RESULTS')
_getOldEnvVar(GC_AUTO_BATCH_MIN, 'GAM_AUTOBATCH')
_getOldEnvVar(GC_BATCH_SIZE, 'GAM_BATCH_SIZE')
_getOldEnvVar(GC_DEVICE_MAX_RESULTS, 'GAM_DEVICE_MAX_RESULTS')
_getOldEnvVar(GC_DRIVE_MAX_RESULTS, 'GAM_DRIVE_MAX_RESULTS')
_getOldEnvVar(GC_MEMBER_MAX_RESULTS, 'GAM_MEMBER_MAX_RESULTS')
_getOldEnvVar(GC_USER_MAX_RESULTS, 'GAM_USER_MAX_RESULTS')
_getOldEnvVar(GC_CSV_HEADER_FILTER, 'GAM_CSV_HEADER_FILTER')
_getOldEnvVar(GC_CSV_ROW_FILTER, 'GAM_CSV_ROW_FILTER')
_getOldEnvVar(GC_TLS_MIN_VERSION, 'GAM_TLS_MIN_VERSION')
@@ -683,6 +682,7 @@ def SetGlobalVariables():
_getOldEnvVar(GC_CA_FILE, 'GAM_CA_FILE')
_getOldSignalFile(GC_DEBUG_LEVEL, 'debug.gam', filePresentValue=4, fileAbsentValue=0)
_getOldSignalFile(GC_NO_BROWSER, 'nobrowser.txt')
_getOldSignalFile(GC_OAUTH_BROWSER, 'oauthbrowser.txt')
# _getOldSignalFile(GC_NO_CACHE, u'nocache.txt')
# _getOldSignalFile(GC_CACHE_DISCOVERY_ONLY, u'allcache.txt', filePresentValue=False, fileAbsentValue=True)
_getOldSignalFile(GC_NO_CACHE, 'allcache.txt', filePresentValue=False, fileAbsentValue=True)
@@ -724,6 +724,30 @@ def SetGlobalVariables():
GM_Globals[GM_CACHE_DISCOVERY_ONLY] = False
return True
TIME_OFFSET_UNITS = [('day', 86400), ('hour', 3600), ('minute', 60), ('second', 1)]
def getLocalGoogleTimeOffset(testLocation='www.googleapis.com'):
localUTC = datetime.datetime.now(datetime.timezone.utc)
try:
# we disable SSL verify so we can still get time even if clock
# is way off. This could be spoofed / MitM but we'll fail for those
# situations everywhere else but here.
badhttp = _createHttpObj()
badhttp.disable_ssl_certificate_validation = True
googleUTC = dateutil.parser.parse(badhttp.request('https://'+testLocation, 'HEAD')[0]['date'])
except (httplib2.ServerNotFoundError, RuntimeError, ValueError) as e:
systemErrorExit(4, str(e))
offset = remainder = int(abs((localUTC-googleUTC).total_seconds()))
timeoff = []
for tou in TIME_OFFSET_UNITS:
uval, remainder = divmod(remainder, tou[1])
if uval:
timeoff.append('{0} {1}{2}'.format(uval, tou[0], 's' if uval != 1 else ''))
if not timeoff:
timeoff.append('less than 1 second')
nicetime = ', '.join(timeoff)
return (offset, nicetime)
def doGAMCheckForUpdates(forceCheck=False):
def _gamLatestVersionNotAvailable():
@@ -741,8 +765,7 @@ def doGAMCheckForUpdates(forceCheck=False):
return
check_url = GAM_LATEST_RELEASE # latest full release
headers = {'Accept': 'application/vnd.github.v3.text+json'}
simplehttp = _createHttpObj()
simplehttp.timeout = 10
simplehttp = _createHttpObj(timeout=10)
try:
(_, c) = simplehttp.request(check_url, 'GET', headers=headers)
try:
@@ -778,10 +801,24 @@ def doGAMCheckForUpdates(forceCheck=False):
except (httplib2.HttpLib2Error, httplib2.ServerNotFoundError, RuntimeError, socket.timeout):
return
def getOSPlatform():
myos = platform.system()
if myos == 'Linux':
pltfrm = ' '.join(distro.linux_distribution(full_distribution_name=False)).title()
elif myos == 'Windows':
pltfrm = ' '.join(platform.win32_ver())
elif myos == 'Darwin':
myos = 'MacOS'
mac_ver = platform.mac_ver()[0]
minor_ver = int(mac_ver.split('.')[1]) # macver 10.14.6 == minor_ver 14
codename = MACOS_CODENAMES.get(minor_ver, '')
pltfrm = ' '.join([codename, mac_ver])
else:
pltfrm = platform.platform()
return '%s %s' % (myos, pltfrm)
def doGAMVersion(checkForArgs=True):
force_check = False
simple = False
extended = False
force_check = extended = simple = timeOffset = False
testLocation = 'www.googleapis.com'
if checkForArgs:
i = 2
@@ -795,6 +832,10 @@ def doGAMVersion(checkForArgs=True):
i += 1
elif myarg == 'extended':
extended = True
timeOffset = True
i += 1
elif myarg == 'timeoffset':
timeOffset = True
i += 1
elif myarg == 'location':
testLocation = sys.argv[i+1]
@@ -808,7 +849,12 @@ def doGAMVersion(checkForArgs=True):
print(version_data.format(gam_version, GAM_URL, gam_author, sys.version_info[0],
sys.version_info[1], sys.version_info[2], struct.calcsize('P')*8,
sys.version_info[3], googleapiclient.__version__,
platform.platform(), platform.machine(), GM_Globals[GM_GAM_PATH]))
getOSPlatform(), platform.machine(), GM_Globals[GM_GAM_PATH]))
if timeOffset:
offset, nicetime = getLocalGoogleTimeOffset(testLocation)
print(MESSAGE_YOUR_SYSTEM_TIME_DIFFERS_FROM_GOOGLE_BY % nicetime)
if offset > MAX_LOCAL_GOOGLE_TIME_OFFSET:
systemErrorExit(4, 'Please fix your system time.')
if force_check:
doGAMCheckForUpdates(forceCheck=True)
if extended:
@@ -858,7 +904,8 @@ def getSvcAcctCredentials(scopes, act_as):
GM_Globals[GM_OAUTH2SERVICE_JSON_DATA] = json.loads(json_string)
credentials = google.oauth2.service_account.Credentials.from_service_account_info(GM_Globals[GM_OAUTH2SERVICE_JSON_DATA])
credentials = credentials.with_scopes(scopes)
credentials = credentials.with_subject(act_as)
if act_as:
credentials = credentials.with_subject(act_as)
GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID] = GM_Globals[GM_OAUTH2SERVICE_JSON_DATA]['client_id']
return credentials
except (ValueError, KeyError):
@@ -1082,6 +1129,22 @@ def callGAPI(service, function,
except TypeError as e:
systemErrorExit(4, str(e))
def getPageSize(service, function, kwargs):
"""Gets maximum maxResults value for API call. Uses value from discovery if
it exists, otherwise value from MAX_RESULTS_API_EXCEPTIONS, otherwise None"""
method = getattr(service, function)
api_id = method(**kwargs).methodId
for resource in service._rootDesc.get('resources', {}).values():
for a_method in resource.get('methods', {}).values():
if a_method.get('id') == api_id:
if not a_method.get('parameters') or a_method['parameters'].get('pageSize') or not a_method['parameters'].get('maxResults'):
# make sure API call supports maxResults. For now we don't care to
# set pageSize since all known pageSize API calls have
# default pageSize == max pageSize
return
return {'maxResults': a_method['parameters']['maxResults'].get('maximum', MAX_RESULTS_API_EXCEPTIONS.get(api_id, None))}
def callGAPIpages(service, function, items='items',
page_message=None, message_attribute=None,
soft_errors=False, throw_reasons=None, retry_reasons=None,
@@ -1100,7 +1163,6 @@ def callGAPIpages(service, function, items='items',
Template strings allow for dynamic content to be inserted during paging.
Supported template strings:
%%num_items%% : The number of items in the current page.
%%total_items%% : The current number of items discovered across all
pages.
%%first_item%% : In conjunction with `message_attribute` arg, will
@@ -1124,6 +1186,10 @@ def callGAPIpages(service, function, items='items',
Returns:
A list of all items received from all paged responses.
"""
if 'maxResults' not in kwargs and 'pageSize' not in kwargs:
page_key = getPageSize(service, function, kwargs)
if page_key:
kwargs.update(page_key)
all_items = []
page_token = None
total_items = 0
@@ -1147,8 +1213,7 @@ def callGAPIpages(service, function, items='items',
# Show a paging message to the user that indicates paging progress
if page_message:
show_message = page_message.replace('%%num_items%%', str(num_page_items))
show_message = show_message.replace('%%total_items%%', str(total_items))
show_message = page_message.replace('%%total_items%%', str(total_items))
if message_attribute:
first_item = page_items[0] if num_page_items > 0 else {}
last_item = page_items[-1] if num_page_items > 1 else first_item
@@ -1236,25 +1301,35 @@ def getOauth2TxtStorageCredentials():
return creds
def getValidOauth2TxtCredentials(force_refresh=False):
"""Gets OAuth2 credentials which are guaranteed to be fresh and valid."""
credentials = getOauth2TxtStorageCredentials()
if (credentials and credentials.expired) or force_refresh:
retries = 3
for n in range(1, retries+1):
try:
credentials.refresh(google_auth_httplib2.Request(_createHttpObj()))
writeCredentials(credentials)
break
except google.auth.exceptions.RefreshError as e:
systemErrorExit(18, str(e))
except (google.auth.exceptions.TransportError, httplib2.ServerNotFoundError, RuntimeError) as e:
if n != retries:
waitOnFailure(n, retries, str(e))
continue
systemErrorExit(4, str(e))
elif credentials is None or not credentials.valid:
doRequestOAuth()
"""Gets OAuth2 credentials which are guaranteed to be fresh and valid.
Locks during read and possible write so that only one process will
attempt refresh/write when running in parallel. """
lock_file = '%s.lock' % GC_Values[GC_OAUTH2_TXT]
lock = FileLock(lock_file)
with lock:
credentials = getOauth2TxtStorageCredentials()
if (credentials and credentials.expired) or force_refresh:
retries = 3
for n in range(1, retries+1):
try:
credentials.refresh(google_auth_httplib2.Request(_createHttpObj()))
writeCredentials(credentials)
break
except google.auth.exceptions.RefreshError as e:
systemErrorExit(18, str(e))
except (google.auth.exceptions.TransportError, httplib2.ServerNotFoundError, RuntimeError) as e:
if n != retries:
waitOnFailure(n, retries, str(e))
continue
systemErrorExit(4, str(e))
elif credentials is None or not credentials.valid:
doRequestOAuth()
credentials = getOauth2TxtStorageCredentials()
if not GM_Globals[GM_WINDOWS]:
try:
os.remove(lock_file)
except IOError:
pass
return credentials
def getService(api, http):
@@ -1311,7 +1386,6 @@ def buildGAPIObject(api):
GM_Globals[GM_CURRENT_API_USER] = None
credentials = getValidOauth2TxtCredentials()
credentials.user_agent = GAM_INFO
#http = credentials.authorize(httplib2.Http(cache=GM_Globals[GM_CACHE_DIR]))
http = google_auth_httplib2.AuthorizedHttp(credentials, _createHttpObj(cache=GM_Globals[GM_CACHE_DIR]))
service = getService(api, http)
if GC_Values[GC_DOMAIN]:
@@ -1468,7 +1542,38 @@ def buildGmailGAPIObject(user):
userEmail = convertUIDtoEmailAddress(user)
return (userEmail, buildGAPIServiceObject('gmail', userEmail))
def printPassFail(description, result):
print(' {0:74} {1}'.format(description, result))
def doCheckServiceAccount(users):
something_failed = False
print('Computer clock status:')
timeOffset, nicetime = getLocalGoogleTimeOffset()
if timeOffset < MAX_LOCAL_GOOGLE_TIME_OFFSET:
time_status = 'PASS'
else:
time_status = 'FAIL'
something_failed = True
printPassFail(MESSAGE_YOUR_SYSTEM_TIME_DIFFERS_FROM_GOOGLE_BY % nicetime, time_status)
oa2 = googleapiclient.discovery.build('oauth2', 'v1', _createHttpObj())
print('Service Account Private Key Authentication:')
# We are explicitly not doing DwD here, just confirming service account can auth
auth_error = ''
try:
credentials = getSvcAcctCredentials([USERINFO_EMAIL_SCOPE], None)
request = google_auth_httplib2.Request(_createHttpObj())
credentials.refresh(request)
sa_token_info = callGAPI(oa2, 'tokeninfo', access_token=credentials.token)
if sa_token_info:
sa_token_result = 'PASS'
else:
sa_token_result = 'FAIL'
something_failed = True
except google.auth.exceptions.RefreshError as e:
sa_token_result = 'FAIL'
something_failed = True
auth_error = str(e.args[0])
printPassFail('Authenticating...%s' % auth_error, sa_token_result)
all_scopes = []
for _, scopes in list(API_SCOPE_MAPPING.items()):
for scope in scopes:
@@ -1476,25 +1581,40 @@ def doCheckServiceAccount(users):
all_scopes.append(scope)
all_scopes.sort()
for user in users:
user = user.lower()
all_scopes_pass = True
print('User: %s' % (user))
oa2 = googleapiclient.discovery.build('oauth2', 'v1', _createHttpObj())
print('Domain-Wide Delegation authentication as %s:' % (user))
for scope in all_scopes:
try:
credentials = getSvcAcctCredentials([scope], user)
request = google_auth_httplib2.Request(_createHttpObj())
credentials.refresh(request)
result = 'PASS'
except (httplib2.ServerNotFoundError, RuntimeError) as e:
systemErrorExit(4, e)
except google.auth.exceptions.RefreshError:
# try with and without email scope
for scopes in [[scope, USERINFO_EMAIL_SCOPE], [scope]]:
try:
credentials = getSvcAcctCredentials(scopes, user)
credentials.refresh(request)
break
except (httplib2.ServerNotFoundError, RuntimeError) as e:
systemErrorExit(4, e)
except google.auth.exceptions.RefreshError:
continue
if credentials.token:
token_info = callGAPI(oa2, 'tokeninfo', access_token=credentials.token)
if scope in token_info.get('scope', '').split(' ') and \
user == token_info.get('email', user).lower():
result = 'PASS'
else:
result = 'FAIL'
all_scopes_pass = False
else:
result = 'FAIL'
all_scopes_pass = False
print(' Scope: {0:60} {1}'.format(scope, result))
printPassFail(scope, result)
service_account = GM_Globals[GM_OAUTH2SERVICE_ACCOUNT_CLIENT_ID]
if all_scopes_pass:
print('\nAll scopes passed!\nService account %s is fully authorized.' % service_account)
return
user_domain = user[user.find('@')+1:]
# Tack on email scope for more accurate checking
all_scopes.append(USERINFO_EMAIL_SCOPE)
scopes_failed = '''Some scopes failed! Please go to:
https://admin.google.com/%s/AdminHome?#OGX:ManageOauthClients
@@ -1604,7 +1724,7 @@ def showReport():
sys.exit(1)
if fullData == 0:
continue
page_message = 'Got %%num_items%% Users\n'
page_message = 'Got %%total_items%% Users\n'
usage = callGAPIpages(rep.userUsageReport(), 'get', 'usageReports', page_message=page_message, throw_reasons=[GAPI_INVALID],
date=tryDate, userKey=userKey, customerId=customerId, orgUnitID=orgUnitId, filters=filters, parameters=parameters)
break
@@ -1709,7 +1829,7 @@ def showReport():
report = 'token'
elif report == 'group':
report = 'groups'
page_message = 'Got %%num_items%% items\n'
page_message = 'Got %%total_items%% items\n'
activities = callGAPIpages(rep.activities(), 'list', 'items',
page_message=page_message,
applicationName=report, userKey=userKey,
@@ -2343,8 +2463,7 @@ def buildRoleIdToNameToIdMap():
cd = buildGAPIObject('directory')
result = callGAPIpages(cd.roles(), 'list', 'items',
customer=GC_Values[GC_CUSTOMER_ID],
fields='nextPageToken,items(roleId,roleName)',
maxResults=100)
fields='nextPageToken,items(roleId,roleName)')
GM_Globals[GM_MAP_ROLE_ID_TO_NAME] = {}
GM_Globals[GM_MAP_ROLE_NAME_TO_ID] = {}
for role in result:
@@ -2375,8 +2494,7 @@ def buildUserIdToNameMap():
cd = buildGAPIObject('directory')
result = callGAPIpages(cd.users(), 'list', 'users',
customer=GC_Values[GC_CUSTOMER_ID],
fields='nextPageToken,users(id,primaryEmail)',
maxResults=GC_Values[GC_USER_MAX_RESULTS])
fields='nextPageToken,users(id,primaryEmail)')
GM_Globals[GM_MAP_USER_ID_TO_NAME] = {}
for user in result:
GM_Globals[GM_MAP_USER_ID_TO_NAME][user['id']] = user['primaryEmail']
@@ -2854,7 +2972,7 @@ def doPrintCourses():
fieldsList.append('ownerId')
fields = 'nextPageToken,courses({0})'.format(','.join(set(fieldsList))) if fieldsList else None
printGettingAllItems('Courses', None)
page_message = 'Got %%num_items%% Courses...\n'
page_message = 'Got %%total_items%% Courses...\n'
all_courses = callGAPIpages(croom.courses(), 'list', 'courses', page_message=page_message, teacherId=teacherId, studentId=studentId, courseStates=courseStates, fields=fields)
for course in all_courses:
if ownerEmails is not None:
@@ -2881,20 +2999,20 @@ def doPrintCourses():
i += 1
courseId = course['id']
if showAliases:
alias_message = ' Got %%%%num_items%%%% Aliases for course %s%s' % (courseId, currentCount(i, count))
alias_message = ' Got %%%%total_items%%%% Aliases for course %s%s' % (courseId, currentCount(i, count))
course_aliases = callGAPIpages(croom.courses().aliases(), 'list', 'aliases',
page_message=alias_message,
courseId=courseId)
course['Aliases'] = delimiter.join([alias['alias'][2:] for alias in course_aliases])
if showMembers:
if showMembers != 'students':
teacher_message = ' Got %%%%num_items%%%% Teachers for course %s%s' % (courseId, currentCount(i, count))
teacher_message = ' Got %%%%total_items%%%% Teachers for course %s%s' % (courseId, currentCount(i, count))
results = callGAPIpages(croom.courses().teachers(), 'list', 'teachers',
page_message=teacher_message,
courseId=courseId, fields=teachersFields)
_saveParticipants(course, results, 'teachers')
if showMembers != 'teachers':
student_message = ' Got %%%%num_items%%%% Students for course %s%s' % (courseId, currentCount(i, count))
student_message = ' Got %%%%total_items%%%% Students for course %s%s' % (courseId, currentCount(i, count))
results = callGAPIpages(croom.courses().students(), 'list', 'students',
page_message=student_message,
courseId=courseId, fields=studentsFields)
@@ -2939,7 +3057,7 @@ def doPrintCourseParticipants():
systemErrorExit(2, '%s is not a valid argument for "gam print course-participants"' % sys.argv[i])
if not courses:
printGettingAllItems('Courses', None)
page_message = 'Got %%num_items%% Courses...\n'
page_message = 'Got %%total_items%% Courses...\n'
all_courses = callGAPIpages(croom.courses(), 'list', 'courses', page_message=page_message,
teacherId=teacherId, studentId=studentId, courseStates=courseStates, fields='nextPageToken,courses(id,name)')
else:
@@ -2952,12 +3070,12 @@ def doPrintCourseParticipants():
i += 1
courseId = course['id']
if showMembers != 'students':
page_message = ' Got %%%%num_items%%%% Teachers for course %s (%s/%s)' % (courseId, i, count)
page_message = ' Got %%%%total_items%%%% Teachers for course %s (%s/%s)' % (courseId, i, count)
teachers = callGAPIpages(croom.courses().teachers(), 'list', 'teachers', page_message=page_message, courseId=courseId)
for teacher in teachers:
addRowTitlesToCSVfile(flatten_json(teacher, flattened={'courseId': courseId, 'courseName': course['name'], 'userRole': 'TEACHER'}), csvRows, titles)
if showMembers != 'teachers':
page_message = ' Got %%%%num_items%%%% Students for course %s (%s/%s)' % (courseId, i, count)
page_message = ' Got %%%%total_items%%%% Students for course %s (%s/%s)' % (courseId, i, count)
students = callGAPIpages(croom.courses().students(), 'list', 'students', page_message=page_message, courseId=courseId)
for student in students:
addRowTitlesToCSVfile(flatten_json(student, flattened={'courseId': courseId, 'courseName': course['name'], 'userRole': 'STUDENT'}), csvRows, titles)
@@ -3161,7 +3279,10 @@ def changeCalendarAttendees(users):
continue
page_token = None
while True:
events_page = callGAPI(cal.events(), 'list', calendarId=user, pageToken=page_token, timeMin=start_date, timeMax=end_date, showDeleted=False, showHiddenInvitations=False)
events_page = callGAPI(cal.events(), 'list', calendarId=user,
pageToken=page_token, timeMin=start_date,
timeMax=end_date, showDeleted=False,
showHiddenInvitations=False)
print('Got %s items' % len(events_page.get('items', [])))
for event in events_page.get('items', []):
if event['status'] == 'cancelled':
@@ -3842,8 +3963,8 @@ def doCalendarPrintEvents():
systemErrorExit(2, '%s is not a valid argument for "gam calendar <email> printevents"' % sys.argv[i])
page_message = 'Got %%%%total_items%%%% events for %s' % calendarId
results = callGAPIpages(cal.events(), 'list', 'items', page_message=page_message,
maxResults=2500, calendarId=calendarId,
q=q, showDeleted=showDeleted, showHiddenInvitations=showHiddenInvitations,
calendarId=calendarId, q=q, showDeleted=showDeleted,
showHiddenInvitations=showHiddenInvitations,
timeMin=timeMin, timeMax=timeMax, timeZone=timeZone, updatedMin=updatedMin)
for result in results:
row = {'calendarId': calendarId}
@@ -4308,7 +4429,7 @@ def printDriveActivity(users):
feed = callGAPIpages(activity.activities(), 'list', 'activities',
page_message=page_message, source='drive.google.com', userId='me',
drive_ancestorId=drive_ancestorId, groupingStrategy='none',
drive_fileId=drive_fileId, pageSize=GC_Values[GC_ACTIVITY_MAX_RESULTS])
drive_fileId=drive_fileId)
for item in feed:
addRowTitlesToCSVfile(flatten_json(item['combinedEvent']), csvRows, titles)
writeCSVfile(csvRows, titles, 'Drive Activity', todrive)
@@ -4588,7 +4709,7 @@ def printDriveFileList(users):
page_message = ' Got %%%%total_items%%%% files for %s...\n' % user
feed = callGAPIpages(drive.files(), 'list', 'items',
page_message=page_message, soft_errors=True,
q=query, orderBy=orderBy, fields=fields, maxResults=GC_Values[GC_DRIVE_MAX_RESULTS])
q=query, orderBy=orderBy, fields=fields)
for f_file in feed:
a_file = {'Owner': user}
for attrib in f_file:
@@ -4642,7 +4763,7 @@ def doDriveSearch(drive, query=None, quiet=False):
page_message = None
files = callGAPIpages(drive.files(), 'list', 'items',
page_message=page_message,
q=query, fields='nextPageToken,items(id)', maxResults=GC_Values[GC_DRIVE_MAX_RESULTS])
q=query, fields='nextPageToken,items(id)')
ids = list()
for f_file in files:
ids.append(f_file['id'])
@@ -4762,7 +4883,7 @@ def showDriveFileTree(users):
sys.stderr.write('Getting all files for %s...\n' % user)
page_message = ' Got %%%%total_items%%%% files for %s...\n' % user
feed = callGAPIpages(drive.files(), 'list', 'items', page_message=page_message,
q=query, orderBy=orderBy, fields='items(id,title,parents(id),mimeType),nextPageToken', maxResults=GC_Values[GC_DRIVE_MAX_RESULTS])
q=query, orderBy=orderBy, fields='items(id,title,parents(id),mimeType),nextPageToken')
printDriveFolderContents(feed, root_folder, 0)
def deleteEmptyDriveFolders(users):
@@ -4776,7 +4897,7 @@ def deleteEmptyDriveFolders(users):
sys.stderr.write('Getting folders for %s...\n' % user)
page_message = ' Got %%%%total_items%%%% folders for %s...\n' % user
feed = callGAPIpages(drive.files(), 'list', 'items', page_message=page_message,
q=query, fields='items(title,id),nextPageToken', maxResults=GC_Values[GC_DRIVE_MAX_RESULTS])
q=query, fields='items(title,id),nextPageToken')
deleted_empty = False
for folder in feed:
children = callGAPI(drive.children(), 'list',
@@ -4831,6 +4952,13 @@ def getDriveFileAttribute(i, body, parameters, myarg, update=False):
elif myarg == 'ocrlanguage':
parameters[DFA_OCRLANGUAGE] = LANGUAGE_CODES_MAP.get(sys.argv[i+1].lower(), sys.argv[i+1])
i += 2
elif myarg in ['copyrequireswriterpermission', 'restrict', 'restricted']:
if update:
body['copyRequiresWriterPermission'] = getBoolean(sys.argv[i+1], myarg)
i += 2
else:
body['copyRequiresWriterPermission'] = True
i += 1
elif myarg in DRIVEFILE_LABEL_CHOICES_MAP:
body.setdefault('labels', {})
if update:
@@ -7550,6 +7678,14 @@ def getUserAttributes(i, cd, updateCmd):
keyword['value'] = sys.argv[i]
i += 1
appendItemToBodyList(body, 'keywords', keyword)
elif myarg in ['recoveryemail']:
body['recoveryEmail'] = sys.argv[i+1]
i += 2
elif myarg in ['recoveryphone']:
body['recoveryPhone'] = sys.argv[i+1]
if body['recoveryPhone'] and body['recoveryPhone'][0] != '+':
body['recoveryPhone'] = '+' + body['recoveryPhone']
i += 2
elif myarg == 'clearschema':
if not updateCmd:
systemErrorExit(2, '%s is not a valid create user argument.' % sys.argv[i])
@@ -7604,6 +7740,24 @@ def getUserAttributes(i, cd, updateCmd):
body['hashFunction'] = 'crypt'
return body
class ShortURLFlow(google_auth_oauthlib.flow.InstalledAppFlow):
def authorization_url(self, **kwargs):
long_url, state = super(ShortURLFlow, self).authorization_url(**kwargs)
simplehttp = _createHttpObj(timeout=10)
url_shortnr = 'https://gam-shortn.appspot.com/create'
headers = {'Content-Type': 'application/json',
'user-agent': GAM_INFO}
try:
resp, content = simplehttp.request(url_shortnr, 'POST', '{"long_url": "%s"}' % long_url, headers=headers)
except:
return long_url, state
if resp.status != 200:
return long_url, state
try:
return json.loads(content).get('short_url', long_url), state
except:
return long_url, state
def _run_oauth_flow(client_id, client_secret, scopes, access_type, login_hint=None):
client_config = {
'installed': {
@@ -7615,11 +7769,11 @@ def _run_oauth_flow(client_id, client_secret, scopes, access_type, login_hint=No
}
}
flow = google_auth_oauthlib.flow.InstalledAppFlow.from_client_config(client_config, scopes)
flow = ShortURLFlow.from_client_config(client_config, scopes)
kwargs = {'access_type': access_type}
if login_hint:
kwargs['login_hint'] = login_hint
if GC_Values[GC_NO_BROWSER]:
if not GC_Values[GC_OAUTH_BROWSER]:
flow.run_console(
authorization_prompt_message=MESSAGE_CONSOLE_AUTHORIZATION_PROMPT,
authorization_code_message=MESSAGE_CONSOLE_AUTHORIZATION_CODE,
@@ -8863,7 +9017,7 @@ def doCreateUser():
def GroupIsAbuseOrPostmaster(emailAddr):
return emailAddr.startswith('abuse@') or emailAddr.startswith('postmaster@')
GROUP_SETTINGS_LIST_PATTERN = re.compile(r'([A-Z][A-Z_]+[A-Z]?):')
GROUP_SETTINGS_LIST_PATTERN = re.compile(r'([A-Z][A-Z_]+[A-Z]?)')
def getGroupAttrValue(myarg, value, gs_object, gs_body, function):
if myarg == 'collaborative':
@@ -9502,7 +9656,7 @@ def doUpdateGroup():
result = callGAPIpages(cd.members(), 'list', 'members',
page_message=page_message,
throw_reasons=GAPI_MEMBERS_THROW_REASONS,
groupKey=group, roles=listRoles, fields=listFields, maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
groupKey=group, roles=listRoles, fields=listFields)
if not result:
print('Group already has 0 members')
return
@@ -9683,7 +9837,10 @@ def doUpdateMobile():
query = resourceIds[6:]
fields = 'nextPageToken,mobiledevices(resourceId,email)'
page_message = 'Got %%total_items%% mobile devices...\n'
devices = callGAPIpages(cd.mobiledevices(), 'list', page_message=page_message, customerId=GC_Values[GC_CUSTOMER_ID], items='mobiledevices', query=query, fields=fields)
devices = callGAPIpages(cd.mobiledevices(), 'list',
page_message=page_message,
customerId=GC_Values[GC_CUSTOMER_ID],
items='mobiledevices', query=query, fields=fields)
else:
devices = [{'resourceId': resourceIds, 'email': ['not set']}]
doit = True
@@ -10088,9 +10245,13 @@ def doGetUserInfo(user_email=None):
else:
print('Last login time: %s' % user['lastLoginTime'])
if 'orgUnitPath' in user:
print('Google Org Unit Path: %s\n' % user['orgUnitPath'])
print('Google Org Unit Path: %s' % user['orgUnitPath'])
if 'thumbnailPhotoUrl' in user:
print('Photo URL: %s\n' % user['thumbnailPhotoUrl'])
if 'recoveryPhone' in user:
print('Recovery Phone: %s' % user['recoveryPhone'])
if 'recoveryEmail' in user:
print('Recovery Email: %s' % user['recoveryEmail'])
if 'notes' in user:
print('Notes:')
notes = user['notes']
@@ -10328,7 +10489,7 @@ def doGetGroupInfo(group_name=None):
for groupm in groups:
print(' %s: %s' % (groupm['name'], groupm['email']))
if getUsers:
members = callGAPIpages(cd.members(), 'list', 'members', groupKey=group_name, fields='nextPageToken,members(email,id,role,type)', maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
members = callGAPIpages(cd.members(), 'list', 'members', groupKey=group_name, fields='nextPageToken,members(email,id,role,type)')
print('Members:')
for member in members:
print(' %s: %s (%s)' % (member.get('role', ROLE_MEMBER).lower(), member.get('email', member['id']), member['type'].lower()))
@@ -10467,6 +10628,8 @@ def doGetCrosInfo():
print('CrOS Device: {0} ({1} of {2})'.format(deviceId, i, device_count))
if 'notes' in cros:
cros['notes'] = cros['notes'].replace('\n', '\\n')
if 'autoUpdateExpiration' in cros:
cros['autoUpdateExpiration'] = utils.formatTimestampYMD(cros['autoUpdateExpiration'])
_checkTPMVulnerability(cros)
if guess_aue:
_guessAUE(cros, guessedAUEs)
@@ -10516,7 +10679,7 @@ def doGetCrosInfo():
if deviceFile:
downloadfilename = os.path.join(targetFolder, 'cros-logs-{0}-{1}.zip'.format(deviceId, deviceFile['createTime']))
_, content = cd._http.request(deviceFile['downloadUrl'])
writeFile(downloadfilename, content, continueOnError=True)
writeFile(downloadfilename, content, mode='wb', continueOnError=True)
print('Downloaded: {0}'.format(downloadfilename))
elif downloadfile:
print('ERROR: no files to download.')
@@ -10808,7 +10971,7 @@ def doGetOrgInfo(name=None, return_attrib=None):
page_message = 'Got %%total_items%% Users: %%first_item%% - %%last_item%%\n'
users = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
message_attribute='primaryEmail', customer=GC_Values[GC_CUSTOMER_ID], query=orgUnitPathQuery(name, checkSuspended),
fields='users(primaryEmail,orgUnitPath),nextPageToken', maxResults=GC_Values[GC_USER_MAX_RESULTS])
fields='users(primaryEmail,orgUnitPath),nextPageToken')
if checkSuspended is None:
print('Users:')
elif not checkSuspended:
@@ -11051,7 +11214,7 @@ def doUndeleteUser():
else:
print('Looking up UID for %s...' % user)
deleted_users = callGAPIpages(cd.users(), 'list', 'users',
customer=GC_Values[GC_CUSTOMER_ID], showDeleted=True, maxResults=GC_Values[GC_USER_MAX_RESULTS])
customer=GC_Values[GC_CUSTOMER_ID], showDeleted=True)
matching_users = list()
for deleted_user in deleted_users:
if str(deleted_user['primaryEmail']).lower() == user:
@@ -11423,6 +11586,8 @@ USER_ARGUMENT_TO_PROPERTY_MAP = {
'posix': ['posixAccounts',],
'posixaccounts': ['posixAccounts',],
'primaryemail': ['primaryEmail',],
'recoveryemail': ['recoveryEmail',],
'recoveryphone': ['recoveryPhone',],
'relation': ['relations',],
'relations': ['relations',],
'ssh': ['sshPublicKeys',],
@@ -11543,7 +11708,7 @@ def doPrintUsers():
all_users = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
message_attribute='primaryEmail', customer=customer, domain=domain, fields=fields,
showDeleted=deleted_only, orderBy=orderBy, sortOrder=sortOrder, viewType=viewType,
query=query, projection=projection, customFieldMask=customFieldMask, maxResults=GC_Values[GC_USER_MAX_RESULTS])
query=query, projection=projection, customFieldMask=customFieldMask)
for user in all_users:
if email_parts and ('primaryEmail' in user):
user_email = user['primaryEmail']
@@ -11693,7 +11858,6 @@ def doPrintGroups():
titles = []
csvRows = []
addFieldTitleToCSVfile('email', GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP, cdfieldsList, fieldsTitles, titles)
maxResults = None
roles = []
getSettings = sortHeaders = False
while i < len(sys.argv):
@@ -11714,7 +11878,7 @@ def doPrintGroups():
usemember = None
i += 2
elif myarg == 'maxresults':
maxResults = getInteger(sys.argv[i+1], myarg, minVal=0)
# deprecated argument
i += 2
elif myarg == 'delimiter':
aliasDelimiter = memberDelimiter = sys.argv[i+1]
@@ -11792,12 +11956,11 @@ def doPrintGroups():
if not ownersCountOnly:
addTitlesToCSVfile(['Owners',], titles)
printGettingAllItems('Groups', None)
page_message = 'Got %%num_items%% Groups: %%first_item%% - %%last_item%%\n'
page_message = 'Got %%total_items%% Groups: %%first_item%% - %%last_item%%\n'
entityList = callGAPIpages(cd.groups(), 'list', 'groups',
page_message=page_message, message_attribute='email',
customer=customer, domain=usedomain, userKey=usemember, query=usequery,
fields='nextPageToken,groups({0})'.format(cdfields),
maxResults=maxResults)
fields='nextPageToken,groups({0})'.format(cdfields))
i = 0
count = len(entityList)
for groupEntity in entityList:
@@ -11812,12 +11975,12 @@ def doPrintGroups():
group[fieldsTitles[field]] = groupEntity[field]
if roles:
sys.stderr.write(' Getting %s for %s (%s/%s)\n' % (roles, groupEmail, i, count))
page_message = ' Got %%num_items%% members: %%first_item%% - %%last_item%%\n'
page_message = ' Got %%total_items%% members: %%first_item%% - %%last_item%%\n'
validRoles, listRoles, listFields = _getRoleVerification(roles, 'nextPageToken,members(email,id,role)')
groupMembers = callGAPIpages(cd.members(), 'list', 'members',
page_message=page_message, message_attribute='email',
soft_errors=True,
groupKey=groupEmail, roles=listRoles, fields=listFields, maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
groupKey=groupEmail, roles=listRoles, fields=listFields)
if members:
membersList = []
membersCount = 0
@@ -12001,10 +12164,10 @@ def doPrintAliases():
if doUsers:
for query in queries:
printGettingAllItems('User Aliases', query)
page_message = 'Got %%num_items%% Users %%first_item%% - %%last_item%%\n'
page_message = 'Got %%total_items%% Users %%first_item%% - %%last_item%%\n'
all_users = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
message_attribute='primaryEmail', customer=GC_Values[GC_CUSTOMER_ID], query=query,
fields='nextPageToken,users({0})'.format(','.join(userFields)), maxResults=GC_Values[GC_USER_MAX_RESULTS])
fields='nextPageToken,users({0})'.format(','.join(userFields)))
for user in all_users:
for alias in user.get('aliases', []):
csvRows.append({'Alias': alias, 'Target': user['primaryEmail'], 'TargetType': 'User'})
@@ -12012,7 +12175,7 @@ def doPrintAliases():
csvRows.append({'NonEditableAlias': alias, 'Target': user['primaryEmail'], 'TargetType': 'User'})
if doGroups:
printGettingAllItems('Group Aliases', None)
page_message = 'Got %%num_items%% Groups %%first_item%% - %%last_item%%\n'
page_message = 'Got %%total_items%% Groups %%first_item%% - %%last_item%%\n'
all_groups = callGAPIpages(cd.groups(), 'list', 'groups', page_message=page_message,
message_attribute='email', customer=GC_Values[GC_CUSTOMER_ID],
fields='nextPageToken,groups({0})'.format(','.join(groupFields)))
@@ -12027,6 +12190,7 @@ def doPrintGroupMembers():
cd = buildGAPIObject('directory')
todrive = False
membernames = False
includeDerivedMembership = False
customer = GC_Values[GC_CUSTOMER_ID]
checkSuspended = usedomain = usemember = usequery = None
roles = []
@@ -12078,6 +12242,9 @@ def doPrintGroupMembers():
elif myarg in ['suspended', 'notsuspended']:
checkSuspended = myarg == 'suspended'
i += 1
elif myarg == 'includederivedmembership':
includeDerivedMembership = True
i += 1
else:
systemErrorExit(2, '%s is not a valid argument for "gam print group-members"' % sys.argv[i])
if not groups_to_get:
@@ -12093,7 +12260,8 @@ def doPrintGroupMembers():
validRoles, listRoles, listFields = _getRoleVerification(','.join(roles), fields)
group_members = callGAPIpages(cd.members(), 'list', 'members',
soft_errors=True,
groupKey=group_email, roles=listRoles, fields=listFields, maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
includeDerivedMembership=includeDerivedMembership,
groupKey=group_email, roles=listRoles, fields=listFields)
for member in group_members:
if not _checkMemberRoleIsSuspended(member, validRoles, checkSuspended):
continue
@@ -12151,7 +12319,7 @@ def doPrintVaultMatters():
else:
systemErrorExit(3, '%s is not a valid argument to "gam print matters"' % myarg)
printGettingAllItems('Vault Matters', None)
page_message = 'Got %%num_items%% Vault Matters...\n'
page_message = 'Got %%total_items%% Vault Matters...\n'
matters = callGAPIpages(v.matters(), 'list', 'matters', page_message=page_message, view=view)
for matter in matters:
addRowTitlesToCSVfile(flatten_json(matter), csvRows, titles)
@@ -12284,10 +12452,10 @@ def doPrintMobileDevices():
systemErrorExit(2, '%s is not a valid argument for "gam print mobile"' % sys.argv[i])
for query in queries:
printGettingAllItems('Mobile Devices', query)
page_message = 'Got %%num_items%% Mobile Devices...\n'
page_message = 'Got %%total_items%% Mobile Devices...\n'
all_mobile = callGAPIpages(cd.mobiledevices(), 'list', 'mobiledevices', page_message=page_message,
customerId=GC_Values[GC_CUSTOMER_ID], query=query, projection=projection, fields=fields,
orderBy=orderBy, sortOrder=sortOrder, maxResults=GC_Values[GC_DEVICE_MAX_RESULTS])
orderBy=orderBy, sortOrder=sortOrder)
for mobile in all_mobile:
row = {}
for attrib in mobile:
@@ -12404,7 +12572,7 @@ def doPrintCrosActivity():
page_message = 'Got %%total_items%% CrOS Devices...\n'
all_cros = callGAPIpages(cd.chromeosdevices(), 'list', 'chromeosdevices', page_message=page_message,
query=query, customerId=GC_Values[GC_CUSTOMER_ID], projection='FULL',
fields=fields, maxResults=GC_Values[GC_DEVICE_MAX_RESULTS], orgUnitPath=orgUnitPath)
fields=fields, orgUnitPath=orgUnitPath)
for cros in all_cros:
row = {}
for attrib in cros:
@@ -12585,7 +12753,7 @@ def doPrintCrosDevices():
page_message = 'Got %%total_items%% CrOS Devices...\n'
all_cros = callGAPIpages(cd.chromeosdevices(), 'list', 'chromeosdevices', page_message=page_message,
query=query, customerId=GC_Values[GC_CUSTOMER_ID], projection=projection, orgUnitPath=orgUnitPath,
orderBy=orderBy, sortOrder=sortOrder, fields=fields, maxResults=GC_Values[GC_DEVICE_MAX_RESULTS])
orderBy=orderBy, sortOrder=sortOrder, fields=fields)
for cros in all_cros:
_checkTPMVulnerability(cros)
if guess_aue:
@@ -12594,6 +12762,8 @@ def doPrintCrosDevices():
for cros in all_cros:
if 'notes' in cros:
cros['notes'] = cros['notes'].replace('\n', '\\n')
if 'autoUpdateExpiration' in cros:
cros['autoUpdateExpiration'] = utils.formatTimestampYMD(cros['autoUpdateExpiration'])
for cpuStatusReport in cros.get('cpuStatusReports', []):
for tempInfo in cpuStatusReport.get('cpuTemperatureInfo', []):
tempInfo['label'] = tempInfo['label'].strip()
@@ -12602,6 +12772,8 @@ def doPrintCrosDevices():
for cros in all_cros:
if 'notes' in cros:
cros['notes'] = cros['notes'].replace('\n', '\\n')
if 'autoUpdateExpiration' in cros:
cros['autoUpdateExpiration'] = utils.formatTimestampYMD(cros['autoUpdateExpiration'])
row = {}
for attrib in cros:
if attrib not in set(['kind', 'etag', 'tpmVersionInfo', 'recentUsers', 'activeTimeRanges',
@@ -12989,7 +13161,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
page_message = 'Got %%%%total_items%%%% %s...' % member_type_message
validRoles, listRoles, listFields = _getRoleVerification(member_type, 'nextPageToken,members(email,id,type,status)')
members = callGAPIpages(cd.members(), 'list', 'members', page_message=page_message,
groupKey=group, roles=listRoles, fields=listFields, maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
groupKey=group, roles=listRoles, fields=listFields)
users = []
for member in members:
if ((not groupUserMembersOnly) or (member['type'] == 'USER')) and _checkMemberRoleIsSuspended(member, validRoles, checkSuspended):
@@ -13012,7 +13184,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
page_message = 'Got %%total_items%% Users...'
members = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
customer=GC_Values[GC_CUSTOMER_ID], fields='nextPageToken,users(primaryEmail,orgUnitPath)',
query=query, maxResults=GC_Values[GC_USER_MAX_RESULTS])
query=query)
ou = ou.lower()
for member in members:
if ou == member.get('orgUnitPath', '').lower():
@@ -13034,7 +13206,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
page_message = 'Got %%total_items%% Users...'
members = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
customer=GC_Values[GC_CUSTOMER_ID], fields='nextPageToken,users(primaryEmail)',
query=query, maxResults=GC_Values[GC_USER_MAX_RESULTS])
query=query)
for member in members:
users.append(member['primaryEmail'])
if not silent:
@@ -13053,7 +13225,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
page_message = 'Got %%total_items%% Users...'
members = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
customer=GC_Values[GC_CUSTOMER_ID], fields='nextPageToken,users(primaryEmail,suspended)',
query=query, maxResults=GC_Values[GC_USER_MAX_RESULTS])
query=query)
for member in members:
email = member['primaryEmail']
if (checkSuspended is None or checkSuspended == member['suspended']) and email not in usersSet:
@@ -13119,7 +13291,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
page_message = 'Got %%total_items%% Users...'
all_users = callGAPIpages(cd.users(), 'list', 'users', page_message=page_message,
customer=GC_Values[GC_CUSTOMER_ID], query=query,
fields='nextPageToken,users(primaryEmail)', maxResults=GC_Values[GC_USER_MAX_RESULTS])
fields='nextPageToken,users(primaryEmail)')
for member in all_users:
users.append(member['primaryEmail'])
if not silent:
@@ -13129,8 +13301,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
printGettingAllItems('CrOS Devices', None)
page_message = 'Got %%total_items%% CrOS Devices...'
all_cros = callGAPIpages(cd.chromeosdevices(), 'list', 'chromeosdevices', page_message=page_message,
customerId=GC_Values[GC_CUSTOMER_ID], fields='nextPageToken,chromeosdevices(deviceId)',
maxResults=GC_Values[GC_DEVICE_MAX_RESULTS])
customerId=GC_Values[GC_CUSTOMER_ID], fields='nextPageToken,chromeosdevices(deviceId)')
for member in all_cros:
users.append(member['deviceId'])
if not silent:
@@ -13155,7 +13326,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
page_message = 'Got %%total_items%% CrOS Devices...'
members = callGAPIpages(cd.chromeosdevices(), 'list', 'chromeosdevices', page_message=page_message,
customerId=GC_Values[GC_CUSTOMER_ID], fields='nextPageToken,chromeosdevices(deviceId)',
query=query, maxResults=GC_Values[GC_DEVICE_MAX_RESULTS])
query=query)
for member in members:
deviceId = member['deviceId']
if deviceId not in usersSet:
@@ -13221,25 +13392,33 @@ def OAuthInfo():
print('%s: %s' % (key, value))
def doDeleteOAuth():
credentials = getOauth2TxtStorageCredentials()
if credentials is None:
return
simplehttp = _createHttpObj()
params = {'token': credentials.refresh_token}
revoke_uri = 'https://accounts.google.com/o/oauth2/revoke?%s' % urlencode(params)
sys.stderr.write('This OAuth token will self-destruct in 3...')
sys.stderr.flush()
time.sleep(1)
sys.stderr.write('2...')
sys.stderr.flush()
time.sleep(1)
sys.stderr.write('1...')
sys.stderr.flush()
time.sleep(1)
sys.stderr.write('boom!\n')
sys.stderr.flush()
simplehttp.request(revoke_uri, 'GET')
os.remove(GC_Values[GC_OAUTH2_TXT])
lock_file = '%s.lock' % GC_Values[GC_OAUTH2_TXT]
lock = FileLock(lock_file, timeout=10)
with lock:
credentials = getOauth2TxtStorageCredentials()
if credentials is None:
return
simplehttp = _createHttpObj()
params = {'token': credentials.refresh_token}
revoke_uri = 'https://accounts.google.com/o/oauth2/revoke?%s' % urlencode(params)
sys.stderr.write('This OAuth token will self-destruct in 3...')
sys.stderr.flush()
time.sleep(1)
sys.stderr.write('2...')
sys.stderr.flush()
time.sleep(1)
sys.stderr.write('1...')
sys.stderr.flush()
time.sleep(1)
sys.stderr.write('boom!\n')
sys.stderr.flush()
simplehttp.request(revoke_uri, 'GET')
os.remove(GC_Values[GC_OAUTH2_TXT])
if not GM_Globals[GM_WINDOWS]:
try:
os.remove(lock_file)
except IOError:
pass
def writeCredentials(creds):
creds_data = {

View File

@@ -1,4 +1,6 @@
python-dateutil
distro; sys_platform == 'linux'
filelock
google-api-python-client>=1.7.10
google-auth
google-auth-httplib2

15
src/tools/a_atleast_b.py Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python3
#from packaging import version
from distutils.version import LooseVersion
import sys
a = sys.argv[1]
b = sys.argv[2]
#result = version.parse(a) >= version.parse(b)
result = LooseVersion(a) >= LooseVersion(b)
if result:
print('OK: %s is equal or newer than %s' % (a, b))
else:
print('ERROR: %s is older than %s' % (a, b))
sys.exit(not result)

View File

@@ -53,32 +53,25 @@ else
tar xf Python-$BUILD_PYTHON_VERSION.tar.xz
fi
cd Python-$BUILD_PYTHON_VERSION
#if [[ "$dist" == "bionic" ]]; then
# echo "running bionic make clean"
# make clean
# rm Makefile
#fi
echo "Compiling Python $BUILD_PYTHON_VERSION..."
safe_flags="--with-openssl=$mypath/ssl --enable-shared --prefix=$mypath/python --with-ensurepip=upgrade"
unsafe_flags="--enable-optimizations --with-lto"
unsafe_flags="--enable-optimizations --with-lto"
if [ ! -e Makefile ]; then
echo "running configure with safe and unsafe"
./configure $safe_flags $unsafe_flags > /dev/null
fi
timeout 1800 make -j$cpucount -s
make -j$cpucount PROFILE_TASK="-m test.regrtest --pgo -j$(( $cpucount * 2 ))" -s
RESULT=$?
echo "First make exited with $RESULT"
if [ $RESULT != 0 ]; then
#echo "Trying Python compile again without unsafe flags..."
#make clean
#./configure $safe_flags > /dev/null
#make -j$cpucount -s
echo "Trying Python compile again without unsafe flags..."
make clean
./configure $safe_flags > /dev/null
make -j$cpucount -s
echo "Sticking with safe Python for now..."
else
echo "Installing optimized Python..."
make install > /dev/null
fi
echo "Installing Python..."
make install > /dev/null
cd ~
export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
@@ -99,22 +92,12 @@ else
make
sudo make install
fi
if [ ! -d musl=$MUSL_VERSION ]; then
echo "Downloading MUSL $MUSL_VERSION"
wget https://www.musl-libc.org/releases/musl-$MUSL_VERSION.tar.gz
tar xf musl-$MUSL_VERSION.tar.gz
cd musl-$MUSL_VERSION
./configure
make
sudo make install
fi
$pip install git+https://github.com/JonathonReinhart/staticx.git@master
fi
cd $whereibelong
fi
echo "Upgrading pip packages..."
$pip freeze > upgrades.txt
$pip install --upgrade -r upgrades.txt
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
$pip install --upgrade -r src/requirements.txt
$pip install --upgrade pyinstaller

View File

@@ -29,4 +29,5 @@ else
du -h gam/gam
time $gam version extended
fi
fi

View File

@@ -3,15 +3,24 @@ whereibelong=$(pwd)
#echo "Brew installing xz..."
#brew install xz > /dev/null
#brew upgrade
cd ~/pybuild
if [ ! -f python-$BUILD_PYTHON_VERSION-macosx10.9.pkg ]; then
wget --quiet https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/python-$BUILD_PYTHON_VERSION-macosx10.9.pkg
fi
sudo installer -pkg python-$BUILD_PYTHON_VERSION-macosx10.9.pkg -target /
#if [ ! -f python-$BUILD_PYTHON_VERSION-macosx10.9.pkg ]; then
# wget --quiet https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/python-$BUILD_PYTHON_VERSION-macosx10.9.pkg
#fi
#sudo installer -pkg python-$BUILD_PYTHON_VERSION-macosx10.9.pkg -target /
brew install openssl@1.1
brew upgrade python
export python=python3
export pip=pip3
echo "Python location:"
which $python
# Compile latest OpenSSL
#if [ ! -d openssl-$BUILD_OPENSSL_VERSION ]; then
# wget --quiet https://www.openssl.org/source/openssl-$BUILD_OPENSSL_VERSION.tar.gz
@@ -58,14 +67,12 @@ export pip=pip3
#python=~/python/bin/python3
#pip=~/python/bin/pip3
$python -V
cd $whereibelong
export PATH=/usr/local/opt/python/libexec/bin:$PATH
$pip install --upgrade pip
$pip freeze > upgrades.txt
$pip install --upgrade -r upgrades.txt
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
$pip install --upgrade -r src/requirements.txt
$pip install --upgrade pyinstaller

View File

@@ -1,4 +1,6 @@
cd src
echo "MacOS Version Info According to Python:"
python -c "import platform; print(platform.mac_ver())"
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath=gam $GAMOS-gam.spec
export gam="gam/gam"
export gampath=gam
@@ -7,6 +9,7 @@ export GAMVERSION=`gam/gam version simple`
cp LICENSE gam
cp whatsnew.txt gam
cp GamCommands.txt gam
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM.tar.xz
MACOSVERSION=$(defaults read loginwindow SystemVersionStampAsString)
GAM_ARCHIVE=gam-$GAMVERSION-$GAMOS-$PLATFORM-MacOS$MACOSVERSION.tar.xz
rm gam/lastupdatecheck.txt
tar cfJ $GAM_ARCHIVE gam/

View File

@@ -2,30 +2,32 @@ echo "Installing Net-Framework-Core..."
export mypath=$(pwd)
until powershell Install-WindowsFeature Net-Framework-Core; do echo "trying again..."; done
cd ~/pybuild
#export exefile=Win32OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
#if [ ! -e $exefile ]; then
# echo "Downloading $exefile..."
# wget --quiet https://slproweb.com/download/$exefile
#fi
#echo "Installing $exefile..."
#powershell ".\\${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl"
export exefile=Win32OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
if [ ! -e $exefile ]; then
echo "Downloading $exefile..."
wget --quiet https://slproweb.com/download/$exefile
fi
echo "Installing $exefile..."
powershell ".\\${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl"
cinst -y --forcex86 python3
until cinst -y wixtoolset; do echo "trying again..."; done
#echo "OpenSSL dlls..."
#ls -alRF /c/ssl
#echo "c drive"
#ls -al /c/
#echo "Python dlls..."
#ls -al /c/Python37/DLLs
#until cp -v /c/ssl/*.dll /c/Python37/DLLs; do echo "trying again..."; done
echo "OpenSSL dlls..."
ls -alRF /c/ssl
echo "c drive"
ls -al /c/
echo "Python dlls..."
ls -al /c/Python37/DLLs
until cp -v /c/ssl/*.dll /c/Python37/DLLs; do echo "trying again..."; done
export PATH=$PATH:/c/Python37/scripts
cd $mypath
pip install --upgrade pip
pip freeze > upgrades.txt
pip install --upgrade -r upgrades.txt
pip install --upgrade -r src/requirements.txt
export python=/c/Python37/python.exe
export pip=pip
#pip install --upgrade pyinstaller
$pip install --upgrade pip
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
$pip install --upgrade -r src/requirements.txt
#$pip install --upgrade pyinstaller
# Install PyInstaller from source and build bootloader
# to try and avoid getting flagged as malware since
# lots of malware uses PyInstaller default bootloader
@@ -36,10 +38,11 @@ tar xf PyInstaller-$PYINSTALLER_VERSION.tar.gz
cd PyInstaller-$PYINSTALLER_VERSION/bootloader
echo "bootloader before:"
md5sum ../PyInstaller/bootloader/Windows-32bit/*
/c/python37/python ./waf all --target-arch=32bit
$python ./waf all --target-arch=32bit
echo "bootloader after:"
md5sum ../PyInstaller/bootloader/Windows-32bit/*
echo "PATH: $PATH"
cd ..
/c/python37/python setup.py install
$python setup.py install
echo "cd to $mypath"
cd $mypath

View File

@@ -2,26 +2,42 @@ echo "Installing Net-Framework-Core..."
export mypath=$(pwd)
until powershell Install-WindowsFeature Net-Framework-Core; do echo "trying again..."; done
cd ~/pybuild
#export exefile=Win64OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
#if [ ! -e $exefile ]; then
# echo "Downloading $exefile..."
# wget --quiet https://slproweb.com/download/$exefile
#fi
#echo "Installing $exefile..."
#powershell ".\\${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl"
export exefile=Win64OpenSSL_Light-${BUILD_OPENSSL_VERSION//./_}.exe
if [ ! -e $exefile ]; then
echo "Downloading $exefile..."
wget --quiet https://slproweb.com/download/$exefile
fi
echo "Installing $exefile..."
powershell ".\\${exefile} /silent /sp- /suppressmsgboxes /DIR=C:\\ssl"
cinst -y python3
until cinst -y wixtoolset; do echo "trying again..."; done
#echo "OpenSSL dlls..."
#ls -alRF /c/ssl
#echo "c drive"
#ls -al /c
#echo "Python dlls..."
#ls -al /c/Python37/DLLs
#until cp -v /c/ssl/*.dll /c/Python37/DLLs; do echo "trying again..."; done
until cp -v /c/ssl/libcrypto-1_1-x64.dll /c/Python37/DLLs/libcrypto-1_1.dll; do echo "trying again..."; done
until cp -v /c/ssl/libssl-1_1-x64.dll /c/Python37/DLLs/libssl-1_1.dll; do echo "trying again..."; done
export PATH=$PATH:/c/Python37/scripts
cd $mypath
pip install --upgrade pip
pip freeze > upgrades.txt
pip install --upgrade -r upgrades.txt
pip install --upgrade -r src/requirements.txt
pip install --upgrade pyinstaller
export python=/c/Python37/python.exe
export pip=pip
$pip install --upgrade pip
$pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 $pip install -U
$pip install --upgrade -r src/requirements.txt
#$pip install --upgrade pyinstaller
# Install PyInstaller from source and build bootloader
# to try and avoid getting flagged as malware since
# lots of malware uses PyInstaller default bootloader
# https://stackoverflow.com/questions/53584395/how-to-recompile-the-bootloader-of-pyinstaller
echo "Downloading PyInstaller..."
wget --quiet https://github.com/pyinstaller/pyinstaller/releases/download/v$PYINSTALLER_VERSION/PyInstaller-$PYINSTALLER_VERSION.tar.gz
tar xf PyInstaller-$PYINSTALLER_VERSION.tar.gz
cd PyInstaller-$PYINSTALLER_VERSION/bootloader
echo "bootloader before:"
md5sum ../PyInstaller/bootloader/Windows-64bit/*
$python ./waf all --target-arch=64bit
echo "bootloader after:"
md5sum ../PyInstaller/bootloader/Windows-64bit/*
echo "PATH: $PATH"
cd ..
$python setup.py install
echo "cd to $mypath..."
until cp -v /c/ssl/*.dll /c/Python37/DLLs; do echo "trying again..."; done
cd $mypath

View File

@@ -6,7 +6,7 @@ import platform
import re
gam_author = 'Jay Lee <jay0lee@gmail.com>'
gam_version = '4.91'
gam_version = '4.95'
gam_license = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
GAM_URL = 'https://git.io/gam'
@@ -39,6 +39,8 @@ FN_LAST_UPDATE_CHECK_TXT = 'lastupdatecheck.txt'
MY_CUSTOMER = 'my_customer'
# See https://support.google.com/drive/answer/37603
MAX_GOOGLE_SHEET_CELLS = 5000000
MAX_LOCAL_GOOGLE_TIME_OFFSET = 30
SKUS = {
'1010010001': {
'product': '101001', 'aliases': ['identity', 'cloudidentity'], 'displayName': 'Cloud Identity'},
@@ -154,6 +156,8 @@ API_VER_MAPPING = {
'vault': 'v1',
}
USERINFO_EMAIL_SCOPE = 'https://www.googleapis.com/auth/userinfo.email'
API_SCOPE_MAPPING = {
'alertcenter': ['https://www.googleapis.com/auth/apps.alerts',],
'appsactivity': ['https://www.googleapis.com/auth/activity',
@@ -228,6 +232,7 @@ DRIVEFILE_FIELDS_CHOICES_MAP = {
'cancomment': 'canComment',
'canreadrevisions': 'canReadRevisions',
'copyable': 'copyable',
'copyrequireswriterpermission': 'copyRequiresWriterPermission',
'createddate': 'createdDate',
'createdtime': 'createdDate',
'description': 'description',
@@ -377,18 +382,33 @@ GOOGLEDOC_VALID_EXTENSIONS_MAP = {
MIMETYPE_GA_SPREADSHEET: ['.csv', '.ods', '.pdf', '.xlsx', '.zip'],
}
_MICROSOFT_FORMATS_LIST = [{'mime': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'ext': '.docx'},
{'mime': 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'ext': '.dotx'},
{'mime': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'ext': '.pptx'},
{'mime': 'application/vnd.openxmlformats-officedocument.presentationml.template', 'ext': '.potx'},
{'mime': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'ext': '.xlsx'},
{'mime': 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'ext': '.xltx'},
{'mime': 'application/msword', 'ext': '.doc'},
{'mime': 'application/msword', 'ext': '.dot'},
{'mime': 'application/vnd.ms-powerpoint', 'ext': '.ppt'},
{'mime': 'application/vnd.ms-powerpoint', 'ext': '.pot'},
{'mime': 'application/vnd.ms-excel', 'ext': '.xls'},
{'mime': 'application/vnd.ms-excel', 'ext': '.xlt'}]
MACOS_CODENAMES = {
6: 'Snow Leopard',
7: 'Lion',
8: 'Mountain Lion',
9: 'Mavericks',
10: 'Yosemite',
11: 'El Capitan',
12: 'Sierra',
13: 'High Sierra',
14: 'Mojave',
15: 'Catalina'
}
_MICROSOFT_FORMATS_LIST = [
{'mime': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'ext': '.docx'},
{'mime': 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'ext': '.dotx'},
{'mime': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'ext': '.pptx'},
{'mime': 'application/vnd.openxmlformats-officedocument.presentationml.template', 'ext': '.potx'},
{'mime': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'ext': '.xlsx'},
{'mime': 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'ext': '.xltx'},
{'mime': 'application/msword', 'ext': '.doc'},
{'mime': 'application/msword', 'ext': '.dot'},
{'mime': 'application/vnd.ms-powerpoint', 'ext': '.ppt'},
{'mime': 'application/vnd.ms-powerpoint', 'ext': '.pot'},
{'mime': 'application/vnd.ms-excel', 'ext': '.xls'},
{'mime': 'application/vnd.ms-excel', 'ext': '.xlt'}
]
DOCUMENT_FORMATS_MAP = {
'csv': [{'mime': 'text/csv', 'ext': '.csv'}],
@@ -534,17 +554,21 @@ CROS_ARGUMENT_TO_PROPERTY_MAP = {
'annotateduser': ['annotatedUser',],
'asset': ['annotatedAssetId',],
'assetid': ['annotatedAssetId',],
'autoupdateexpiration': ['autoUpdateExpiration',],
'bootmode': ['bootMode',],
'cpustatusreports': ['cpuStatusReports',],
'devicefiles': ['deviceFiles',],
'deviceid': ['deviceId',],
'dockmacaddress': ['dockMacAddress',],
'diskvolumereports': ['diskVolumeReports',],
'ethernetmacaddress': ['ethernetMacAddress',],
'ethernetmacaddress0': ['ethernetMacAddress0',],
'firmwareversion': ['firmwareVersion',],
'lastenrollmenttime': ['lastEnrollmentTime',],
'lastsync': ['lastSync',],
'location': ['annotatedLocation',],
'macaddress': ['macAddress',],
'manufacturedate': ['manufactureDate',],
'meid': ['meid',],
'model': ['model',],
'notes': ['notes',],
@@ -586,12 +610,16 @@ CROS_SCALAR_PROPERTY_PRINT_ORDER = [
'osVersion',
'bootMode',
'meid',
'dockMacAddress',
'ethernetMacAddress',
'ethernetMacAddress0',
'macAddress',
'systemRamTotal',
'lastEnrollmentTime',
'orderNumber',
'manufactureDate',
'supportEndDate',
'autoUpdateExpiration',
'guessedAUEDate',
'guessedAUEModel',
'tpmVersionInfo',
@@ -757,8 +785,6 @@ GM_Globals = {
#
# Global variables defined by environment variables/signal files
#
# When retrieving lists of Google Drive activities from API, how many should be retrieved in each chunk
GC_ACTIVITY_MAX_RESULTS = 'activity_max_results'
# Automatically generate gam batch command if number of users specified in gam users xxx command exceeds this number
# Default: 0, don't automatically generate gam batch commands
GC_AUTO_BATCH_MIN = 'auto_batch_min'
@@ -780,19 +806,15 @@ GC_CUSTOMER_ID = 'customer_id'
GC_DEBUG_LEVEL = 'debug_level'
# ID Token decoded from OAuth 2.0 refresh token response. Includes hd (domain) and email of authorized user
GC_DECODED_ID_TOKEN = 'decoded_id_token'
# When retrieving lists of ChromeOS/Mobile devices from API, how many should be retrieved in each chunk
GC_DEVICE_MAX_RESULTS = 'device_max_results'
# Domain obtained from gam.cfg or oauth2.txt
GC_DOMAIN = 'domain'
# Google Drive download directory
GC_DRIVE_DIR = 'drive_dir'
# When retrieving lists of Drive files/folders from API, how many should be retrieved in each chunk
GC_DRIVE_MAX_RESULTS = 'drive_max_results'
# When retrieving lists of Google Group members from API, how many should be retrieved in each chunk
GC_MEMBER_MAX_RESULTS = 'member_max_results'
# If no_browser is False, writeCSVfile won't open a browser when todrive is set
# and doRequestOAuth prints a link and waits for the verification code when oauth2.txt is being created
GC_NO_BROWSER = 'no_browser'
# oauth_browser forces usage of web server OAuth flow that proved problematic.
GC_OAUTH_BROWSER = 'oauth_browser'
# Disable GAM API caching
GC_NO_CACHE = 'no_cache'
# Disable GAM update check
@@ -811,8 +833,6 @@ GC_SHOW_COUNTS_MIN = 'show_counts_min'
GC_SHOW_GETTINGS = 'show_gettings'
# GAM config directory containing json discovery files
GC_SITE_DIR = 'site_dir'
# When retrieving lists of Users from API, how many should be retrieved in each chunk
GC_USER_MAX_RESULTS = 'user_max_results'
# CSV Columns GAM should show on CSV output
GC_CSV_HEADER_FILTER = 'csv_header_filter'
# CSV Rows GAM should filter
@@ -826,7 +846,6 @@ GC_CA_FILE = 'ca_file'
tls_min = "TLSv1_2" if hasattr(ssl.SSLContext(), "minimum_version") else None
GC_Defaults = {
GC_ACTIVITY_MAX_RESULTS: 100,
GC_AUTO_BATCH_MIN: 0,
GC_BATCH_SIZE: 50,
GC_CACHE_DIR: '',
@@ -837,22 +856,19 @@ GC_Defaults = {
GC_CUSTOMER_ID: MY_CUSTOMER,
GC_DEBUG_LEVEL: 0,
GC_DECODED_ID_TOKEN: '',
GC_DEVICE_MAX_RESULTS: 100,
GC_DOMAIN: '',
GC_DRIVE_DIR: '',
GC_DRIVE_MAX_RESULTS: 1000,
GC_MEMBER_MAX_RESULTS: 200,
GC_NO_BROWSER: False,
GC_NO_CACHE: False,
GC_NO_UPDATE_CHECK: False,
GC_NUM_THREADS: 25,
GC_OAUTH_BROWSER: False,
GC_OAUTH2_TXT: _FN_OAUTH2_TXT,
GC_OAUTH2SERVICE_JSON: _FN_OAUTH2SERVICE_JSON,
GC_SECTION: '',
GC_SHOW_COUNTS_MIN: 0,
GC_SHOW_GETTINGS: True,
GC_SITE_DIR: '',
GC_USER_MAX_RESULTS: 500,
GC_CSV_HEADER_FILTER: '',
GC_CSV_ROW_FILTER: '',
GC_TLS_MIN_VERSION: tls_min,
@@ -877,7 +893,6 @@ GC_VAR_TYPE = 'type'
GC_VAR_LIMITS = 'lmit'
GC_VAR_INFO = {
GC_ACTIVITY_MAX_RESULTS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 500)},
GC_AUTO_BATCH_MIN: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (0, None)},
GC_BATCH_SIZE: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 1000)},
GC_CACHE_DIR: {GC_VAR_TYPE: GC_TYPE_DIRECTORY},
@@ -888,22 +903,19 @@ GC_VAR_INFO = {
GC_CUSTOMER_ID: {GC_VAR_TYPE: GC_TYPE_STRING},
GC_DEBUG_LEVEL: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (0, None)},
GC_DECODED_ID_TOKEN: {GC_VAR_TYPE: GC_TYPE_STRING},
GC_DEVICE_MAX_RESULTS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 1000)},
GC_DOMAIN: {GC_VAR_TYPE: GC_TYPE_STRING},
GC_DRIVE_DIR: {GC_VAR_TYPE: GC_TYPE_DIRECTORY},
GC_DRIVE_MAX_RESULTS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 1000)},
GC_MEMBER_MAX_RESULTS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 10000)},
GC_NO_BROWSER: {GC_VAR_TYPE: GC_TYPE_BOOLEAN},
GC_NO_CACHE: {GC_VAR_TYPE: GC_TYPE_BOOLEAN},
GC_NO_UPDATE_CHECK: {GC_VAR_TYPE: GC_TYPE_BOOLEAN},
GC_NUM_THREADS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, None)},
GC_OAUTH_BROWSER: {GC_VAR_TYPE: GC_TYPE_BOOLEAN},
GC_OAUTH2_TXT: {GC_VAR_TYPE: GC_TYPE_FILE},
GC_OAUTH2SERVICE_JSON: {GC_VAR_TYPE: GC_TYPE_FILE},
GC_SECTION: {GC_VAR_TYPE: GC_TYPE_STRING},
GC_SHOW_COUNTS_MIN: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (0, None)},
GC_SHOW_GETTINGS: {GC_VAR_TYPE: GC_TYPE_BOOLEAN},
GC_SITE_DIR: {GC_VAR_TYPE: GC_TYPE_DIRECTORY},
GC_USER_MAX_RESULTS: {GC_VAR_TYPE: GC_TYPE_INTEGER, GC_VAR_LIMITS: (1, 500)},
GC_CSV_HEADER_FILTER: {GC_VAR_TYPE: GC_TYPE_HEADERFILTER},
GC_CSV_ROW_FILTER: {GC_VAR_TYPE: GC_TYPE_ROWFILTER},
GC_TLS_MIN_VERSION: {GC_VAR_TYPE: GC_TYPE_STRING},
@@ -937,6 +949,7 @@ MESSAGE_NO_TRANSFER_LACK_OF_DISK_SPACE = 'Cowardly refusing to perform migration
MESSAGE_RESULTS_TOO_LARGE_FOR_GOOGLE_SPREADSHEET = 'Results are too large for Google Spreadsheets. Uploading as a regular CSV file.'
MESSAGE_SERVICE_NOT_APPLICABLE = 'Service not applicable for this address: {0}. Please make sure service is enabled for user and run\n\ngam user <user> check serviceaccount\n\nfor further instructions'
MESSAGE_INSTRUCTIONS_OAUTH2SERVICE_JSON = 'Please run\n\ngam create project\ngam user <user> check serviceaccount\n\nto create and configure a service account.'
MESSAGE_YOUR_SYSTEM_TIME_DIFFERS_FROM_GOOGLE_BY = 'Your system time differs from Google by %s'
# oauth errors
OAUTH2_TOKEN_ERRORS = [
'access_denied',
@@ -1199,3 +1212,19 @@ LANGUAGE_CODES_MAP = {
'ur': 'ur', 'uz': 'uz', 'vi': 'vi', 'wo': 'wo', 'xh': 'xh', 'yi': 'yi', 'yo': 'yo', #Urdu, Uzbek, Vietnamese, Wolof, Xhosa, Yiddish, Yoruba
'zh-cn': 'zh-CN', 'zh-hk': 'zh-HK', 'zh-tw': 'zh-TW', 'zu': 'zu', #Chinese (Simplified), Chinese (Hong Kong/Traditional), Chinese (Taiwan/Traditional), Zulu
}
# maxResults exception values for API list calls. Should only be listed if:
# - discovery doc does not specify maximum value (we use maximum value if it
# exists, not this)
# - actual max API returns with maxResults=<bigNum> > default API returns
# when maxResults isn't specified (we should use default otherwise by not
# setting maxResults)
MAX_RESULTS_API_EXCEPTIONS = {
'calendar.acl.list': 250,
'calendar.calendarList.list': 250,
'calendar.events.list': 2500,
'calendar.settings.list': 250,
'directory.chromeosdevices.list': 200,
'drive.files.list': 1000,
}