mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-04 14:21:39 +00:00
Compare commits
69 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bc6c8bca0 | ||
|
|
fc1e81a01d | ||
|
|
eebfaaf373 | ||
|
|
652223d9bc | ||
|
|
e75664fd2e | ||
|
|
556278b216 | ||
|
|
f9bfaa98bb | ||
|
|
b6bd2da6ce | ||
|
|
7c36a6b601 | ||
|
|
413924b11a | ||
|
|
251883dae5 | ||
|
|
7e4d0da8fb | ||
|
|
3fc2aeed4d | ||
|
|
7f4f785f0b | ||
|
|
9f5920989d | ||
|
|
62bceb30c5 | ||
|
|
8c736e52ac | ||
|
|
2669079a31 | ||
|
|
cbfd93e440 | ||
|
|
ffd1c297e5 | ||
|
|
1a11cb04f9 | ||
|
|
438fd5b59a | ||
|
|
2fef5e2cfa | ||
|
|
db8ad38fd3 | ||
|
|
cbb2722291 | ||
|
|
be1c7f2167 | ||
|
|
61f4a137b0 | ||
|
|
dac9e91428 | ||
|
|
83c64f1f71 | ||
|
|
443f4e707b | ||
|
|
70bf2a05f3 | ||
|
|
33a4747677 | ||
|
|
a4cce17767 | ||
|
|
67cd03d3f1 | ||
|
|
1f88c18f94 | ||
|
|
d2fc706b17 | ||
|
|
b5e5786813 | ||
|
|
38e741c788 | ||
|
|
e9a0b85682 | ||
|
|
5ab3602c2a | ||
|
|
dd4bf7b144 | ||
|
|
922326c5ce | ||
|
|
c69be414ca | ||
|
|
10bc47402c | ||
|
|
193e42cf22 | ||
|
|
e0f58e5264 | ||
|
|
f14e48320c | ||
|
|
474fcd33a6 | ||
|
|
a2a9ffc895 | ||
|
|
b02416b32c | ||
|
|
fa52d9e89e | ||
|
|
6383aa594a | ||
|
|
54eb59c27b | ||
|
|
3877f8309b | ||
|
|
d8b0681831 | ||
|
|
cd8303dbea | ||
|
|
ab51d6e931 | ||
|
|
b7e402dca2 | ||
|
|
842040a8b3 | ||
|
|
1205da5d34 | ||
|
|
f40824fedd | ||
|
|
67fa0cbc61 | ||
|
|
e029c77f76 | ||
|
|
2b23ae4e67 | ||
|
|
c8ecc23c9c | ||
|
|
94f8959879 | ||
|
|
2cdb8eb44d | ||
|
|
ebc1d1ecb3 | ||
|
|
d9e99334d2 |
35
.travis.yml
35
.travis.yml
@@ -2,10 +2,11 @@ if: tag IS blank
|
||||
|
||||
env:
|
||||
global:
|
||||
- BUILD_PYTHON_VERSION=3.7.3
|
||||
- BUILD_PYTHON_VERSION=3.7.4
|
||||
- BUILD_OPENSSL_VERSION=1.1.1c
|
||||
- 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="
|
||||
- secure: "szcjWHPr0Bf1KCkyTrV5Fu3ADhWk+pg8YWucjXHdybmhaQIKG7iBNg8LJ5d0OBTwAg31wK4ZgyLVSa2gKrAZ3UeDjykJFsR711xDSQOod51Wrgqu4FbXDewE817DUk3Cwe1l5DCu3/fjEw4vbm8B/qb7iMTRKCq6hJd97FwT5oauP0QHNPer9JjrW4F0Hk9ttkgEU2dXWvBMsTJsDOGNI3ddABE2HskxV4T4thelDYGKBDHhUOAsRwSjXgWy77Tvz98psPIvd+6+WPYNRdRWcPDyAR3Z1O/fNjUymrQI6eMaHoSFrmhDS5lbhjINRfdUmECyfCfIFeLWWiw4g4bq7l+4HBORbei55tAIjhEsxJQoqHi0Q5dD5TFh8IiWqowkFbpvNonMSIpKtB0cyT5jU1G/jRA7MPcIvSrdzHaDkoDNHJgAeZfgjOhzTGYYD19lGIljz5BQBcNFZY2dJbja+Jr4He2CMAOBOdERa4Zn1VyNfOmd8Bn5hu0C9D2ybnSCxjXXq5TRiktR8X7WycVZYfqMZXAwP9FEHVitJ4MZEGUc7S92K5gX4wmjcJjLS+Xo/0nsduQm8PuiMjbcPM7/oGx8Xm1KuSfHdKWMBoaesPaDvRX+YcuiNstXf1DkCWl72TsFABzddlNUMl/s2YSKkCSHAJ5ILqrB28Gx89kzVlg="
|
||||
@@ -36,6 +37,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 Xenial"
|
||||
dist: xenial
|
||||
@@ -61,7 +70,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:
|
||||
@@ -71,8 +80,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"
|
||||
@@ -81,8 +90,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"
|
||||
@@ -91,8 +100,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"
|
||||
@@ -134,10 +143,12 @@ install:
|
||||
script:
|
||||
- $gam version extended
|
||||
- $gam version | grep travis # travis should be part of the path (not /tmp or such)
|
||||
- if [ "$VMTYPE" == "build" ]; then $gam version | grep "Python ${BUILD_PYTHON_VERSION//./\\.}"; fi # We should be building with latest Python
|
||||
- if [ "$VMTYPE" == "build" ]; then $gam version extended | grep "OpenSSL ${BUILD_OPENSSL_VERSION//./\\.}"; fi # We should be using OpenSSL 1.1.1+
|
||||
# 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
|
||||
# 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 $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 if server doesn't support our TLS version
|
||||
- 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")"
|
||||
- if [ "$TRAVIS_EVENT_TYPE" != "pull_request" ]; then export e2e=true; fi
|
||||
- if [ "$e2e" = true ]; then export gam_user=gam-travis-$jid@pdl.jaylee.us; fi
|
||||
@@ -216,6 +227,8 @@ 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"
|
||||
|
||||
@@ -813,7 +813,9 @@ Example: gam csv Users.csv gam update user "~primaryEmail" address type work uns
|
||||
Each user (~primaryEmail, e.g. foo@bar.com) would have their work address updated
|
||||
|
||||
gam create project [<EmailAddress>] [<ProjectID>]
|
||||
gam create project [admin <EmailAddress>] [project <ProjectID>] [parent <String>]
|
||||
gam use project [<EmailAddress>] [<ProjectID>]
|
||||
gam use project [admin <EmailAddress>] [project <ProjectID>]
|
||||
gam update project [<EmailAddress>] [gam|<ProjectID>|(filter <String>)]
|
||||
gam delete project [<EmailAddress>] [gam|<ProjectID>|(filter <String>)]
|
||||
gam show projects [<EmailAddress>] [all|gam|<ProjectID>|(filter <String>)]
|
||||
@@ -872,7 +874,7 @@ gam info resoldsubscriptions <CustomerID> [customer_auth_token <String>]
|
||||
<ReportsAppList> ::= "<ReportsApp>(,<ReportsApp>)*"
|
||||
|
||||
gam report users|user [todrive] [date <Date>] [fulldatarequired all|<ReportsAppList>]
|
||||
[(user all|<UserItem>)|(orgunit|org|ou <OrgUnitPath>)] [filter|filters <String>] [fields|parameters <String>]
|
||||
[(user <UserItem>)|(orgunit|org|ou <OrgUnitPath>)] [filter|filters <String>] [fields|parameters <String>]
|
||||
gam report customers|customer|domain [todrive] [date <Date>] [fulldatarequired all|<ReportsAppList>]
|
||||
[fields|parameters <String>]
|
||||
gam report admin|calendar|calendars|drive|docs|doc|groups|group|logins|login|mobile|tokens|token [todrive]
|
||||
@@ -945,12 +947,12 @@ gam calendar <CalendarItem> showacl
|
||||
gam calendar <CalendarItem> printacl [todrive]
|
||||
|
||||
<EventNotificationAttribute> ::=
|
||||
notifyattendees|(sendnotifications <Boolean>)|(sendupdates all|enternalonly|none)
|
||||
notifyattendees|(sendnotifications <Boolean>)|(sendupdates all|enternalonly|none)
|
||||
|
||||
The following attributes are equivalent:
|
||||
notifyattendees - sendupdates all
|
||||
sendnotifications false - sendupdates none
|
||||
sendnotifications true - sendupdates all
|
||||
notifyattendees - sendupdates all
|
||||
sendnotifications false - sendupdates none
|
||||
sendnotifications true - sendupdates all
|
||||
|
||||
<EventAttributes> ::=
|
||||
anyonecanaddself|
|
||||
@@ -976,15 +978,15 @@ The following attributes are equivalent:
|
||||
(visibility default|public|prvate)
|
||||
|
||||
<EventSelectProperty:> ::=
|
||||
(after <Time>)|
|
||||
(before <Time>)|
|
||||
includeeleted|
|
||||
includehidden|
|
||||
(query <QueryCalendar>)|
|
||||
(updatedmin <Time>)
|
||||
(after <Time>)|
|
||||
(before <Time>)|
|
||||
includeeleted|
|
||||
includehidden|
|
||||
(query <QueryCalendar>)|
|
||||
(updatedmin <Time>)
|
||||
|
||||
<EventDisplayProperty> ::=
|
||||
(timezone <TimeZone>)
|
||||
(timezone <TimeZone>)
|
||||
|
||||
gam calendar <CalendarItem> addevent <EventAttributes>+ [<EventNotificationAttribute>]
|
||||
gam calendar <CalendarItem> deleteevent id|eventid <EventID> [doit] [<EventNotificationAttribute>]
|
||||
@@ -1048,7 +1050,7 @@ The listlimit <Number> argument limits the number of recent users, time ranges a
|
||||
The start <Date> and end <Date> arguments filter the time ranges.
|
||||
Delimiter defaults to comma.
|
||||
|
||||
gam update mobile <MobileID> action <MobileAction>
|
||||
gam update mobile <MobileID>|query:<QueryMobile> action <MobileAction> [doit] [if_users|match_users <UserTypeEntity>]
|
||||
gam delete mobile <MobileID>
|
||||
gam info mobile <MobileID>
|
||||
gam print mobile [todrive] [(query <QueryMobile>)|(queries <QueryMobileList>)] [basic|full] [orderby <MobileOrderByFieldName> [ascending|descending]]
|
||||
@@ -1092,7 +1094,7 @@ gam create resource <ResourceID> <Name> <ResourceAttributes>*
|
||||
gam update resource <ResourceID> <ResourceAttributes>*
|
||||
gam delete resource <ResourceID>
|
||||
gam info resource <ResourceID>
|
||||
gam print resources [todrive] [allfields] <ResourceFieldName>*
|
||||
gam print resources [todrive] [allfields] <ResourceFieldName>* [query <String>]
|
||||
|
||||
gam create schema|schemas <SchemaName> <SchemaFieldDefinition>+
|
||||
gam update schema <SchemaName> <SchemaFieldDefinition>* (deletefield <FieldName>)*
|
||||
@@ -1173,9 +1175,11 @@ gam print printjobs [todrive] [printer|printerid <PrinterID>]
|
||||
gam create vaultexport|export matter <MatterItem> [name <name>] corpus <drive|mail|groups|hangouts_chat>
|
||||
(accounts <EmailAddressList>) | (orgunit|ou <OrgUnitPath>) | (teamdrives <TeamDriveList>) | (rooms <ChatRoomList>) | everyone
|
||||
[scope <all_data|held_data|unprocessed_data>]
|
||||
[terms <terms>] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] [timezone <TimeZone>] [format mbox|pst]
|
||||
[excludedrafts <Boolean>] [driveversiondate <Date>|<Time>] [includeteamdrives] [includerooms]
|
||||
[includeaccessinfo <Boolean>]
|
||||
[terms <terms>] [start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>] [timezone <TimeZone>]
|
||||
[excludedrafts <Boolean>] [format mbox|pst] [showconfidentialmodecontent <Boolean>]
|
||||
[includerooms]
|
||||
[driveversiondate <Date>|<Time>] [includeshareddrives|includeteamdrives] [includeaccessinfo <Boolean>]
|
||||
[region any|europe|us]
|
||||
gam delete export <MatterItem> <ExportItem>
|
||||
gam info export <MatterItem> <ExportItem>
|
||||
gam print exports [todrive] [matters <MatterItemList>]
|
||||
@@ -1286,10 +1290,20 @@ gam <UserTypeEntity> untrash messages query <QueryGmail> [doit] [max_to_untrash|
|
||||
|
||||
gam <UserTypeEntity> show gmailprofile [todrive]
|
||||
|
||||
gam <UserTypeEntity> draftemail [recipient <EmailAddress>] [subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
|
||||
gam <UserTypeEntity> importemail [recipient <EmailAddress>] [subject <String> [(message <String>)|(file <FileName> [charset <Charset>])]]
|
||||
gam <UserTypeEntity> insertemail [recipient <EmailAddress>] [subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
|
||||
gam <UserTypeEntity> sendemail [recipient <EmailAddress>] [subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
|
||||
gam <UserTypeEntity> draftemail [recipient|to <EmailAddress>] [from <EmailAddress>]
|
||||
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
|
||||
gam <UserTypeEntity> importemail [recipient|to <EmailAddress>] [from <EmailAddress>]
|
||||
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
|
||||
[labels <LabelNameList>] (header <String> <String>)*
|
||||
[deleted] [date <Time>]
|
||||
[nevercheckspam] [processforcalendar]
|
||||
gam <UserTypeEntity> insertemail [recipient|to <EmailAddress>] [from <EmailAddress>]
|
||||
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
|
||||
[labels <LabelNameList>] (header <String> <String>)*
|
||||
[deleted] [date <Time>]
|
||||
gam <UserTypeEntity> sendemail [recipient|to <EmailAddress>] [from <EmailAddress>]
|
||||
[subject <String>] [(message <String>)|(file <FileName> [charset <Charset>])]
|
||||
(header <String> <String>)*
|
||||
|
||||
gam <UserTypeEntity> create|add delegate|delegates <EmailAddress>
|
||||
gam <UserTypeEntity> delegate|delegates to <EmailAddress>
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
"crambo chromebook": "2020-06-01T00:00:00.000Z",
|
||||
"ctl chromebook j41 / j41t": "2023-11-01T00:00:00.000Z",
|
||||
"ctl chromebook nl7": "2023-11-01T00:00:00.000Z",
|
||||
"ctl chromebook nl7 / nl7t-360 / nl7tw-360": "2023-11-01T00:00:00.000Z",
|
||||
"ctl chromebook nl7t-360 / nl7tw-360": "2023-11-01T00:00:00.000Z",
|
||||
"ctl chromebook tab tx1": "2023-08-01T00:00:00.000Z",
|
||||
"ctl chromebook tablet tx1 for education": "2023-08-01T00:00:00.000Z",
|
||||
"ctl chromebox cbx1": "2024-06-01T00:00:00.000Z",
|
||||
@@ -212,10 +212,11 @@
|
||||
"pcmerge chromebookpcm-116e/pcm-116eb": "2020-06-01T00:00:00.000Z",
|
||||
"pcmerge chromebookpcm-116t-432b": "2021-08-01T00:00:00.000Z",
|
||||
"poin2 chromebook 11": "2020-06-01T00:00:00.000Z",
|
||||
"poin2 chromebook 11c": "2022-03-01T00:00:00.000Z",
|
||||
"poin2 chromebook 11c": "2022-11-01T00:00:00.000Z",
|
||||
"poin2 chromebook 14": "2022-03-01T00:00:00.000Z",
|
||||
"positivo chromebook c216b": "2021-08-01T00:00:00.000Z",
|
||||
"positivo chromebook ch1190": "2020-06-01T00:00:00.000Z",
|
||||
"promethean chromebox": "2024-06-01T00:00:00.000Z",
|
||||
"prowise 11.6\" entry line chromebook": "2020-06-01T00:00:00.000Z",
|
||||
"prowise chromebook eduline": "2023-11-01T00:00:00.000Z",
|
||||
"prowise chromebook entryline": "2020-06-01T00:00:00.000Z",
|
||||
@@ -224,11 +225,12 @@
|
||||
"rgs education chromebook": "2020-06-01T00:00:00.000Z",
|
||||
"samsung chromebook": "2018-07-01T00:00:00.000Z",
|
||||
"samsung chromebook - xe303": "2018-07-01T00:00:00.000Z",
|
||||
"samsung chromebook 2 11\"": "2019-06-01T00:00:00.000Z",
|
||||
"samsung chromebook 2 11\" - xe500c12": "2020-06-01T00:00:00.000Z",
|
||||
"samsung chromebook 2 13\"": "2019-06-01T00:00:00.000Z",
|
||||
"samsung chromebook 2 11": "2019-06-01T00:00:00.000Z",
|
||||
"samsung chromebook 2 11 - xe500c12": "2020-06-01T00:00:00.000Z",
|
||||
"samsung chromebook 2 13": "2019-06-01T00:00:00.000Z",
|
||||
"samsung chromebook 3": "2021-06-01T00:00:00.000Z",
|
||||
"samsung chromebook plus": "2023-08-01T00:00:00.000Z",
|
||||
"samsung chromebook plus (lte)": "2024-06-01T00:00:00.000Z",
|
||||
"samsung chromebook plus (v2)": "2024-06-01T00:00:00.000Z",
|
||||
"samsung chromebook pro": "2022-11-01T00:00:00.000Z",
|
||||
"samsung chromebook series 5": "2016-06-01T00:00:00.000Z",
|
||||
|
||||
535
src/gam.py
535
src/gam.py
@@ -55,7 +55,6 @@ from multiprocessing import Pool
|
||||
from multiprocessing import freeze_support
|
||||
from urllib.parse import urlencode, urlparse
|
||||
from passlib.hash import sha512_crypt
|
||||
import dns.resolver
|
||||
import dateutil.parser
|
||||
|
||||
import googleapiclient
|
||||
@@ -110,7 +109,7 @@ google_auth_httplib2.AuthorizedHttp.request = _request_with_user_agent(
|
||||
def _createHttpObj(cache=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(tls_maximum_version=tls_maximum_version, tls_minimum_version=tls_minimum_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)
|
||||
|
||||
def showUsage():
|
||||
@@ -684,6 +683,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)
|
||||
@@ -743,6 +743,7 @@ def doGAMCheckForUpdates(forceCheck=False):
|
||||
check_url = GAM_LATEST_RELEASE # latest full release
|
||||
headers = {'Accept': 'application/vnd.github.v3.text+json'}
|
||||
simplehttp = _createHttpObj()
|
||||
simplehttp.timeout = 10
|
||||
try:
|
||||
(_, c) = simplehttp.request(check_url, 'GET', headers=headers)
|
||||
try:
|
||||
@@ -775,7 +776,7 @@ def doGAMCheckForUpdates(forceCheck=False):
|
||||
sys.exit(0)
|
||||
writeFile(GM_Globals[GM_LAST_UPDATE_CHECK_TXT], str(now_time), continueOnError=True, displayError=forceCheck)
|
||||
return
|
||||
except (httplib2.HttpLib2Error, httplib2.ServerNotFoundError, RuntimeError):
|
||||
except (httplib2.HttpLib2Error, httplib2.ServerNotFoundError, RuntimeError, socket.timeout):
|
||||
return
|
||||
|
||||
def doGAMVersion(checkForArgs=True):
|
||||
@@ -1778,7 +1779,7 @@ def watchGmail(users):
|
||||
break
|
||||
else:
|
||||
topic = gamTopics+str(uuid.uuid4())
|
||||
callGAPI(pubsub.projects().topics(), 'create', name=topic, body={})
|
||||
callGAPI(pubsub.projects().topics(), 'create', name=topic)
|
||||
body = {'policy': {'bindings': [{'members': ['serviceAccount:gmail-api-push@system.gserviceaccount.com'], 'role': 'roles/pubsub.editor'}]}}
|
||||
callGAPI(pubsub.projects().topics(), 'setIamPolicy', resource=topic, body=body)
|
||||
subscriptions = callGAPIpages(pubsub.projects().topics().subscriptions(), 'list', items='subscriptions', topic=topic)
|
||||
@@ -1975,9 +1976,6 @@ def doDelCourse():
|
||||
callGAPI(croom.courses(), 'delete', id=courseId)
|
||||
print('Deleted Course %s' % courseId)
|
||||
|
||||
def _getValidCourseStates(croom):
|
||||
return [state for state in croom._rootDesc['schemas']['Course']['properties']['courseState']['enum'] if state != 'COURSE_STATE_UNSPECIFIED']
|
||||
|
||||
def _getValidatedState(state, validStates):
|
||||
state = state.upper()
|
||||
if state not in validStates:
|
||||
@@ -1998,13 +1996,13 @@ def getCourseAttribute(myarg, value, body, croom, function):
|
||||
elif myarg in ['owner', 'ownerid', 'teacher']:
|
||||
body['ownerId'] = normalizeEmailAddressOrUID(value)
|
||||
elif myarg in ['state', 'status']:
|
||||
validStates = _getValidCourseStates(croom)
|
||||
validStates = _getEnumValuesMinusUnspecified(croom._rootDesc['schemas']['Course']['properties']['courseState']['enum'])
|
||||
body['courseState'] = _getValidatedState(value, validStates)
|
||||
else:
|
||||
systemErrorExit(2, '%s is not a valid argument to "gam %s course"' % (myarg, function))
|
||||
|
||||
def _getCourseStates(croom, value, courseStates):
|
||||
validStates = _getValidCourseStates(croom)
|
||||
validStates = _getEnumValuesMinusUnspecified(croom._rootDesc['schemas']['Course']['properties']['courseState']['enum'])
|
||||
for state in value.replace(',', ' ').split():
|
||||
courseStates.append(_getValidatedState(state, validStates))
|
||||
|
||||
@@ -2085,13 +2083,14 @@ def doGetCustomerInfo():
|
||||
# If customer has changed primary domain customerCreationTime is date
|
||||
# of current primary being added, not customer create date.
|
||||
# We should also get all domains and use oldest date
|
||||
domains = doPrintDomains(return_results=True)
|
||||
oldest = datetime.datetime.strptime(customer_info['customerCreationTime'], '%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
domains = callGAPIitems(cd.domains(), 'list', 'domains',
|
||||
customer=GC_Values[GC_CUSTOMER_ID], fields='domains(creationTime)')
|
||||
for domain in domains:
|
||||
domain_creation = datetime.datetime.strptime(domain['creationTime'], '%Y-%m-%d %H:%M:%S.%f')
|
||||
domain_creation = datetime.datetime.fromtimestamp(int(domain['creationTime'])/1000)
|
||||
if domain_creation < oldest:
|
||||
oldest = domain_creation
|
||||
print('Customer Creation Time: %s' % oldest)
|
||||
print('Customer Creation Time: %s' % oldest.strftime('%Y-%m-%dT%H:%M:%SZ'))
|
||||
print('Default Language: %s' % customer_info.get('language', 'Unset (defaults to en)'))
|
||||
if 'postalAddress' in customer_info:
|
||||
print('Address:')
|
||||
@@ -2170,7 +2169,7 @@ def doDelDomainAlias():
|
||||
domainAliasName = sys.argv[3]
|
||||
callGAPI(cd.domainAliases(), 'delete', customer=GC_Values[GC_CUSTOMER_ID], domainAliasName=domainAliasName)
|
||||
|
||||
def doPrintDomains(return_results=False):
|
||||
def doPrintDomains():
|
||||
cd = buildGAPIObject('directory')
|
||||
todrive = False
|
||||
titles = ['domainName',]
|
||||
@@ -2211,8 +2210,6 @@ def doPrintDomains(return_results=False):
|
||||
titles.append(attr)
|
||||
aliasdomain_attributes[attr] = aliasdomain[attr]
|
||||
csvRows.append(aliasdomain_attributes)
|
||||
if return_results:
|
||||
return csvRows
|
||||
writeCSVfile(csvRows, titles, 'Domains', todrive)
|
||||
|
||||
def doPrintDomainAliases():
|
||||
@@ -4695,11 +4692,24 @@ def deleteDriveFile(users):
|
||||
file_ids = [fileIds,]
|
||||
if not file_ids:
|
||||
print('No files to %s for %s' % (function, user))
|
||||
i = 0
|
||||
j = 0
|
||||
batch_size = 10
|
||||
dbatch = drive.new_batch_http_request(callback=drive_del_result)
|
||||
method = getattr(drive.files(), function)
|
||||
for fileId in file_ids:
|
||||
i += 1
|
||||
print('%s %s for %s (%s/%s)' % (action, fileId, user, i, len(file_ids)))
|
||||
callGAPI(drive.files(), function, fileId=fileId, supportsAllDrives=True)
|
||||
j += 1
|
||||
dbatch.add(method(fileId=fileId, supportsAllDrives=True))
|
||||
if len(dbatch._order) == batch_size:
|
||||
print('%s %s files...' % (action, len(dbatch._order)))
|
||||
dbatch.execute()
|
||||
dbatch = drive.new_batch_http_request(callback=drive_del_result)
|
||||
if len(dbatch._order) > 0:
|
||||
print('%s %s files...' % (action, len(dbatch._order)))
|
||||
dbatch.execute()
|
||||
|
||||
def drive_del_result(request_id, response, exception):
|
||||
if exception:
|
||||
print(exception)
|
||||
|
||||
def printDriveFolderContents(feed, folderId, indent):
|
||||
for f_file in feed:
|
||||
@@ -5346,8 +5356,13 @@ def transferDriveFiles(users):
|
||||
|
||||
def sendOrDropEmail(users, method='send'):
|
||||
body = subject = ''
|
||||
recipient = None
|
||||
labels = None
|
||||
recipient = labels = sender = None
|
||||
kwargs = {}
|
||||
if method in ['insert', 'import']:
|
||||
kwargs['internalDateSource'] = 'receivedTime'
|
||||
if method == 'import':
|
||||
kwargs['neverMarkSpam'] = True
|
||||
msgHeaders = {}
|
||||
i = 4
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
@@ -5361,16 +5376,35 @@ def sendOrDropEmail(users, method='send'):
|
||||
elif myarg == 'subject':
|
||||
subject = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'recipient':
|
||||
elif myarg in ['recipient', 'to']:
|
||||
recipient = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'labels':
|
||||
labels = sys.argv[i+1].split(',')
|
||||
elif myarg == 'from':
|
||||
sender = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'header':
|
||||
msgHeaders[sys.argv[i+1]] = sys.argv[i+2]
|
||||
i += 3
|
||||
elif method in ['insert', 'import'] and myarg == 'labels':
|
||||
labels = shlexSplitList(sys.argv[i+1])
|
||||
i += 2
|
||||
elif method in ['insert', 'import'] and myarg == 'deleted':
|
||||
kwargs['deleted'] = True
|
||||
i += 1
|
||||
elif myarg == 'date':
|
||||
msgHeaders['Date'] = getTimeOrDeltaFromNow(sys.argv[i+1])
|
||||
if method in ['insert', 'import']:
|
||||
kwargs['internalDateSource'] = 'dateHeader'
|
||||
i += 2
|
||||
elif method == 'import' and myarg == 'checkspam':
|
||||
kwargs['neverMarkSpam'] = False
|
||||
i += 1
|
||||
elif method == 'import' and myarg == 'processforcalendar':
|
||||
kwargs['processForCalendar'] = True
|
||||
else:
|
||||
systemErrorExit(2, '%s is not a valid argument for "gam <users> sendemail"' % sys.argv[i])
|
||||
systemErrorExit(2, '%s is not a valid argument for "gam <users> %semail"' % (sys.argv[i], method))
|
||||
for user in users:
|
||||
send_email(subject, body, recipient, user, method, labels)
|
||||
send_email(subject, body, recipient, sender, user, method, labels, msgHeaders, kwargs)
|
||||
|
||||
def doImap(users):
|
||||
enable = getBoolean(sys.argv[4], 'gam <users> imap')
|
||||
@@ -7571,6 +7605,21 @@ 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 = httplib2.Http()
|
||||
simplehttp.timeout = 10
|
||||
url_shortnr = 'https://gam-shortn.appspot.com/create'
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
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
|
||||
return json.loads(content).get('short_url', ''), state
|
||||
|
||||
def _run_oauth_flow(client_id, client_secret, scopes, access_type, login_hint=None):
|
||||
client_config = {
|
||||
'installed': {
|
||||
@@ -7582,11 +7631,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,
|
||||
@@ -7603,11 +7652,17 @@ def getCRMService(login_hint):
|
||||
client_id = '297408095146-fug707qsjv4ikron0hugpevbrjhkmsk7.apps.googleusercontent.com'
|
||||
client_secret = 'qM3dP8f_4qedwzWQE1VR4zzU'
|
||||
credentials = _run_oauth_flow(client_id, client_secret, scopes, 'online', login_hint)
|
||||
http = google_auth_httplib2.AuthorizedHttp(credentials)
|
||||
httpc = google_auth_httplib2.AuthorizedHttp(credentials)
|
||||
return (googleapiclient.discovery.build('cloudresourcemanager', 'v1',
|
||||
http=http, cache_discovery=False,
|
||||
http=httpc, cache_discovery=False,
|
||||
discoveryServiceUrl=googleapiclient.discovery.V2_DISCOVERY_URI),
|
||||
http)
|
||||
httpc)
|
||||
|
||||
# Ugh, v2 doesn't contain all the operations of v1 so we need to use both here.
|
||||
def getCRM2Service(httpc):
|
||||
return googleapiclient.discovery.build('cloudresourcemanager', 'v2',
|
||||
http=httpc, cache_discovery=False,
|
||||
discoveryServiceUrl=googleapiclient.discovery.V2_DISCOVERY_URI)
|
||||
|
||||
def getGAMProjectFile(filepath):
|
||||
file_url = GAM_PROJECT_FILEPATH+filepath
|
||||
@@ -7759,7 +7814,7 @@ def _createClientSecretsOauth2service(httpObj, projectId):
|
||||
|
||||
VALIDEMAIL_PATTERN = re.compile(r'^[^@]+@[^@]+\.[^@]+$')
|
||||
|
||||
def _getValidateLoginHint(login_hint):
|
||||
def _getValidateLoginHint(login_hint=None):
|
||||
while True:
|
||||
if not login_hint:
|
||||
login_hint = input('\nWhat is your G Suite admin email address? ').strip()
|
||||
@@ -7791,11 +7846,29 @@ PROJECTID_FORMAT_REQUIRED = '[a-z][a-z0-9-]{4,28}[a-z0-9]'
|
||||
def _getLoginHintProjectId(createCmd):
|
||||
login_hint = None
|
||||
projectId = None
|
||||
try:
|
||||
login_hint = sys.argv[3]
|
||||
projectId = sys.argv[4]
|
||||
except IndexError:
|
||||
pass
|
||||
parent = None
|
||||
if len(sys.argv) >= 4 and sys.argv[3].lower() not in ['admin', 'project', 'parent']:
|
||||
# legacy "gam create/use project <email> <project-id>
|
||||
try:
|
||||
login_hint = sys.argv[3]
|
||||
projectId = sys.argv[4]
|
||||
except IndexError:
|
||||
pass
|
||||
else:
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'admin':
|
||||
login_hint = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'project':
|
||||
projectId = sys.argv[i+1]
|
||||
i += 2
|
||||
elif createCmd and myarg == 'parent':
|
||||
parent = sys.argv[i+1]
|
||||
i += 2
|
||||
else:
|
||||
systemErrorExit(3, '%s is not a valid argument for "gam %s project", expected one of: admin, project%s' % (myarg, ['use', 'create'][createCmd], ['', ' or parent'][createCmd]))
|
||||
login_hint = _getValidateLoginHint(login_hint)
|
||||
if projectId:
|
||||
if not PROJECTID_PATTERN.match(projectId):
|
||||
@@ -7809,6 +7882,14 @@ def _getLoginHintProjectId(createCmd):
|
||||
if not PROJECTID_PATTERN.match(projectId):
|
||||
systemErrorExit(2, 'Invalid Project ID: {0}, expected <{1}>'.format(projectId, PROJECTID_FORMAT_REQUIRED))
|
||||
crm, httpObj = getCRMService(login_hint)
|
||||
if parent and not parent.startswith('organizations/') and not parent.startswith('folders/'):
|
||||
crm2 = getCRM2Service(httpObj)
|
||||
parent = convertGCPFolderNameToID(parent, crm2)
|
||||
if parent:
|
||||
parent_type, parent_id = parent.split('/')
|
||||
if parent_type[-1] == 's':
|
||||
parent_type = parent_type[:-1] # folders > folder, organizations > organization
|
||||
parent = {'type': parent_type, 'id': parent_id}
|
||||
projects = _getProjects(crm, 'id:{0}'.format(projectId))
|
||||
if not createCmd:
|
||||
if not projects:
|
||||
@@ -7818,9 +7899,29 @@ def _getLoginHintProjectId(createCmd):
|
||||
else:
|
||||
if projects:
|
||||
systemErrorExit(2, 'User: {0}, Project ID: {1}, Duplicate'.format(login_hint, projectId))
|
||||
return (crm, httpObj, login_hint, projectId)
|
||||
return (crm, httpObj, login_hint, projectId, parent)
|
||||
|
||||
PROJECTID_FILTER_REQUIRED = 'gam|<ProjectID>|(filter <String>)'
|
||||
def convertGCPFolderNameToID(parent, crm2):
|
||||
# crm2.folders() is broken requiring pageToken, etc in body, not URL.
|
||||
# for now just use callGAPI and if user has that many folders they'll
|
||||
# just need to be specific.
|
||||
folders = callGAPIitems(crm2.folders(), 'search', items='folders',
|
||||
body={'pageSize': 1000, 'query': 'displayName="%s"' % parent})
|
||||
if not folders:
|
||||
systemErrorExit(1, 'ERROR: No folder found matching displayName=%s' % parent)
|
||||
if len(folders) > 1:
|
||||
print('Multiple matches:')
|
||||
for folder in folders:
|
||||
print(' Name: %s ID: %s' % (folder['name'], folder['displayName']))
|
||||
systemErrorExit(2, 'ERROR: Multiple matching folders, please specify one.')
|
||||
return folders[0]['name']
|
||||
|
||||
def createGCPFolder():
|
||||
login_hint = _getValidateLoginHint()
|
||||
_, httpObj = getCRMService(login_hint)
|
||||
crm2 = getCRM2Service(httpObj)
|
||||
callGAPI(crm2.folders(), 'create', body={'name': sys.argv[3], 'displayName': sys.argv[3]})
|
||||
|
||||
def _getLoginHintProjects(printShowCmd):
|
||||
login_hint = None
|
||||
@@ -7865,9 +7966,11 @@ def _checkForExistingProjectFiles():
|
||||
|
||||
def doCreateProject():
|
||||
_checkForExistingProjectFiles()
|
||||
crm, httpObj, login_hint, projectId = _getLoginHintProjectId(True)
|
||||
crm, httpObj, login_hint, projectId, parent = _getLoginHintProjectId(True)
|
||||
login_domain = login_hint[login_hint.find('@')+1:]
|
||||
body = {'projectId': projectId, 'name': 'GAM Project'}
|
||||
if parent:
|
||||
body['parent'] = parent
|
||||
while True:
|
||||
create_again = False
|
||||
print('Creating project "%s"...' % body['name'])
|
||||
@@ -7890,7 +7993,7 @@ def doCreateProject():
|
||||
except (KeyError, IndexError):
|
||||
systemErrorExit(3, 'you have no rights to create projects for your organization and you don\'t seem to be a super admin! Sorry, there\'s nothing more I can do.')
|
||||
org_policy = callGAPI(crm.organizations(), 'getIamPolicy',
|
||||
resource=organization, body={})
|
||||
resource=organization)
|
||||
if 'bindings' not in org_policy:
|
||||
org_policy['bindings'] = []
|
||||
print('Looks like no one has rights to your Google Cloud Organization. Attempting to give you create rights...')
|
||||
@@ -7940,7 +8043,7 @@ and accept the Terms of Service (ToS). As soon as you've accepted the ToS popup,
|
||||
|
||||
def doUseProject():
|
||||
_checkForExistingProjectFiles()
|
||||
_, httpObj, _, projectId = _getLoginHintProjectId(False)
|
||||
_, httpObj, _, projectId, _ = _getLoginHintProjectId(False)
|
||||
_createClientSecretsOauth2service(httpObj, projectId)
|
||||
|
||||
def doUpdateProjects():
|
||||
@@ -7981,7 +8084,7 @@ def doPrintShowProjects(csvFormat):
|
||||
todrive = True
|
||||
i += 1
|
||||
else:
|
||||
systemErrorExit(2, '%s is not a valid argument for "gam %s projects"' % (myarg, ['show', 'print'][csvFormat]))
|
||||
systemErrorExit(3, '%s is not a valid argument for "gam %s projects"' % (myarg, ['show', 'print'][csvFormat]))
|
||||
if not csvFormat:
|
||||
count = len(projects)
|
||||
print('User: {0}, Show {1} Projects'.format(login_hint, count))
|
||||
@@ -8197,25 +8300,20 @@ VAULT_SEARCH_METHODS_MAP = {
|
||||
'ou': 'ORG_UNIT',
|
||||
'room': 'ROOM',
|
||||
'rooms': 'ROOM',
|
||||
'teamdrive': 'TEAM_DRIVE',
|
||||
'teamdrives': 'TEAM_DRIVE',
|
||||
'shareddrive': 'SHARED_DRIVE',
|
||||
'shareddrives': 'SHARED_DRIVE',
|
||||
'teamdrive': 'SHARED_DRIVE',
|
||||
'teamdrives': 'SHARED_DRIVE',
|
||||
}
|
||||
VAULT_SEARCH_METHODS_LIST = ['accounts', 'orgunit', 'teamdrives', 'rooms', 'everyone']
|
||||
VAULT_SEARCH_METHODS_LIST = ['accounts', 'orgunit', 'shareddrives', 'rooms', 'everyone']
|
||||
|
||||
def doCreateVaultExport():
|
||||
v = buildGAPIObject('vault')
|
||||
allowed_corpuses = v._rootDesc['schemas']['Query']['properties']['corpus']['enum']
|
||||
try:
|
||||
allowed_corpuses.remove('CORPUS_TYPE_UNSPECIFIED')
|
||||
except ValueError:
|
||||
pass
|
||||
allowed_scopes = v._rootDesc['schemas']['Query']['properties']['dataScope']['enum']
|
||||
try:
|
||||
allowed_scopes.remove('DATA_SCOPE_UNSPECIFIED')
|
||||
except ValueError:
|
||||
pass
|
||||
allowed_formats = ['MBOX', 'PST']
|
||||
allowed_corpuses = _getEnumValuesMinusUnspecified(v._rootDesc['schemas']['Query']['properties']['corpus']['enum'])
|
||||
allowed_scopes = _getEnumValuesMinusUnspecified(v._rootDesc['schemas']['Query']['properties']['dataScope']['enum'])
|
||||
allowed_formats = _getEnumValuesMinusUnspecified(v._rootDesc['schemas']['MailExportOptions']['properties']['exportFormat']['enum'])
|
||||
export_format = 'MBOX'
|
||||
showConfidentialModeContent = None # default to not even set
|
||||
matterId = None
|
||||
body = {'query': {'dataScope': 'ALL_DATA'}, 'exportOptions': {}}
|
||||
i = 3
|
||||
@@ -8244,8 +8342,8 @@ def doCreateVaultExport():
|
||||
elif searchMethod == 'ORG_UNIT':
|
||||
body['query']['orgUnitInfo'] = {'orgUnitId': getOrgUnitId(sys.argv[i+1])[1]}
|
||||
i += 2
|
||||
elif searchMethod == 'TEAM_DRIVE':
|
||||
body['query']['teamDriveInfo'] = {'teamDriveIds': sys.argv[i+1].split(',')}
|
||||
elif searchMethod == 'SHARED_DRIVE':
|
||||
body['query']['sharedDriveInfo'] = {'sharedDriveIds': sys.argv[i+1].split(',')}
|
||||
i += 2
|
||||
elif searchMethod == 'ROOM':
|
||||
body['query']['hangoutsChatInfo'] = {'roomId': sys.argv[i+1].split(',')}
|
||||
@@ -8275,8 +8373,8 @@ def doCreateVaultExport():
|
||||
elif myarg in ['driveversiondate']:
|
||||
body['query'].setdefault('driveOptions', {})['versionDate'] = getDateZeroTimeOrFullTime(sys.argv[i+1])
|
||||
i += 2
|
||||
elif myarg in ['includeteamdrives']:
|
||||
body['query'].setdefault('driveOptions', {})['includeTeamDrives'] = getBoolean(sys.argv[i+1], myarg)
|
||||
elif myarg in ['includeshareddrives', 'includeteamdrives']:
|
||||
body['query'].setdefault('driveOptions', {})['includeSharedDrives'] = getBoolean(sys.argv[i+1], myarg)
|
||||
i += 2
|
||||
elif myarg in ['includerooms']:
|
||||
body['query']['hangoutsChatOptions'] = {'includeRooms': getBoolean(sys.argv[i+1], myarg)}
|
||||
@@ -8287,6 +8385,15 @@ def doCreateVaultExport():
|
||||
print('ERROR: export format can be one of %s, got %s' % (', '.join(allowed_formats), export_format))
|
||||
sys.exit(3)
|
||||
i += 2
|
||||
elif myarg in ['showconfidentialmodecontent']:
|
||||
showConfidentialModeContent = getBoolean(sys.argv[i+1], myarg)
|
||||
i += 2
|
||||
elif myarg in ['region']:
|
||||
allowed_regions = _getEnumValuesMinusUnspecified(v._rootDesc['schemas']['ExportOptions']['properties']['region']['enum'])
|
||||
body['exportOptions']['region'] = sys.argv[i+1].upper()
|
||||
if body['exportOptions']['region'] not in allowed_regions:
|
||||
systemErrorExit(3, 'region should be one of %s, got %s' % (', '.join(allowed_regions), body['exportOptions']['region']))
|
||||
i += 2
|
||||
elif myarg in ['includeaccessinfo']:
|
||||
body['exportOptions'].setdefault('driveOptions', {})['includeAccessInfo'] = getBoolean(sys.argv[i+1], myarg)
|
||||
i += 2
|
||||
@@ -8310,6 +8417,8 @@ def doCreateVaultExport():
|
||||
if options_field:
|
||||
body['exportOptions'].pop('driveOptions', None)
|
||||
body['exportOptions'][options_field] = {'exportFormat': export_format}
|
||||
if showConfidentialModeContent is not None:
|
||||
body['exportOptions'][options_field]['showConfidentialModeContent'] = showConfidentialModeContent
|
||||
results = callGAPI(v.matters().exports(), 'create', matterId=matterId, body=body)
|
||||
print('Created export %s' % results['id'])
|
||||
print_json(None, results)
|
||||
@@ -8328,6 +8437,75 @@ def doGetVaultExportInfo():
|
||||
export = callGAPI(v.matters().exports(), 'get', matterId=matterId, exportId=exportId)
|
||||
print_json(None, export)
|
||||
|
||||
def _getCloudStorageObject(s, bucket, object_, local_file=None, expectedMd5=None):
|
||||
if not local_file:
|
||||
local_file = object_
|
||||
if os.path.exists(local_file):
|
||||
sys.stdout.write(' File already exists. ')
|
||||
sys.stdout.flush()
|
||||
if expectedMd5:
|
||||
sys.stdout.write('Verifying %s hash...' % expectedMd5)
|
||||
sys.stdout.flush()
|
||||
if md5MatchesFile(local_file, expectedMd5, False):
|
||||
print('VERIFIED')
|
||||
return
|
||||
print('not verified. Downloading again and over-writing...')
|
||||
else:
|
||||
return # nothing to verify, just assume we're good.
|
||||
print('saving to %s' % local_file)
|
||||
request = s.objects().get_media(bucket=bucket, object=object_)
|
||||
file_path = os.path.dirname(local_file)
|
||||
if not os.path.exists(file_path):
|
||||
os.makedirs(file_path)
|
||||
f = openFile(local_file, 'wb')
|
||||
downloader = googleapiclient.http.MediaIoBaseDownload(f, request)
|
||||
done = False
|
||||
while not done:
|
||||
status, done = downloader.next_chunk()
|
||||
sys.stdout.write(' Downloaded: {0:>7.2%}\r'.format(status.progress()))
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write('\n Download complete. Flushing to disk...\n')
|
||||
# Necessary to make sure file is flushed by both Python and OS
|
||||
# https://stackoverflow.com/a/13762137/1503886
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
closeFile(f)
|
||||
if expectedMd5:
|
||||
f = openFile(local_file, 'rb')
|
||||
sys.stdout.write(' Verifying file hash is %s...' % expectedMd5)
|
||||
sys.stdout.flush()
|
||||
md5MatchesFile(local_file, expectedMd5, True)
|
||||
print('VERIFIED')
|
||||
closeFile(f)
|
||||
|
||||
def md5MatchesFile(local_file, expected_md5, exitOnError):
|
||||
f = openFile(local_file, 'rb')
|
||||
hash_md5 = hashlib.md5()
|
||||
for chunk in iter(lambda: f.read(4096), b""):
|
||||
hash_md5.update(chunk)
|
||||
actual_hash = hash_md5.hexdigest()
|
||||
if exitOnError and actual_hash != expected_md5:
|
||||
systemErrorExit(6, 'actual hash was %s. Exiting on corrupt file.' % actual_hash)
|
||||
return actual_hash == expected_md5
|
||||
|
||||
def doDownloadCloudStorageBucket():
|
||||
bucket_url = sys.argv[3]
|
||||
bucket_regex = r'(takeout-export-[a-f,0-9,-]*)'
|
||||
bucket_match = re.search(bucket_regex, bucket_url)
|
||||
if bucket_match:
|
||||
bucket = bucket_match.group(1)
|
||||
else:
|
||||
systemErrorExit(5, 'Could not find a takeout-export-* bucket in that URL')
|
||||
s = buildGAPIObject('storage')
|
||||
page_message = 'Got %%total_items%% files...'
|
||||
objects = callGAPIpages(s.objects(), 'list', 'items', page_message=page_message, bucket=bucket, projection='noAcl', fields='nextPageToken,items(name,id,md5Hash)')
|
||||
i = 1
|
||||
for object_ in objects:
|
||||
print("%s/%s" % (i, len(objects)))
|
||||
expectedMd5 = base64.b64decode(object_['md5Hash']).hex()
|
||||
_getCloudStorageObject(s, bucket, object_['name'], expectedMd5=expectedMd5)
|
||||
i += 1
|
||||
|
||||
def doDownloadVaultExport():
|
||||
verifyFiles = True
|
||||
extractFiles = True
|
||||
@@ -8372,21 +8550,12 @@ def doDownloadVaultExport():
|
||||
f.flush()
|
||||
os.fsync(f.fileno())
|
||||
closeFile(f)
|
||||
f = openFile(filename, 'rb')
|
||||
if verifyFiles:
|
||||
expected_hash = s_file['md5Hash']
|
||||
sys.stdout.write(' Verifying file hash is %s...' % expected_hash)
|
||||
sys.stdout.flush()
|
||||
hash_md5 = hashlib.md5()
|
||||
for chunk in iter(lambda: f.read(4096), b""):
|
||||
hash_md5.update(chunk)
|
||||
actual_hash = hash_md5.hexdigest()
|
||||
if actual_hash == expected_hash:
|
||||
print('VERIFIED')
|
||||
else:
|
||||
print('ERROR: actual hash was %s. Exiting on corrupt file.' % actual_hash)
|
||||
sys.exit(6)
|
||||
closeFile(f)
|
||||
md5MatchesFile(filename, expected_hash, True)
|
||||
print('VERIFIED')
|
||||
if extractFiles and re.search(r'\.zip$', filename):
|
||||
extract_nested_zip(filename, targetFolder)
|
||||
|
||||
@@ -8710,21 +8879,11 @@ def doCreateUser():
|
||||
def GroupIsAbuseOrPostmaster(emailAddr):
|
||||
return emailAddr.startswith('abuse@') or emailAddr.startswith('postmaster@')
|
||||
|
||||
def mapCollaborativeACL(myarg, value):
|
||||
value = value.lower().replace('_', '')
|
||||
if value in COLLABORATIVE_ACL_CHOICES:
|
||||
return COLLABORATIVE_ACL_CHOICES[value]
|
||||
systemErrorExit(3, 'allowed choices for %s are %s, got %s' % (myarg, ', '.join(sorted(COLLABORATIVE_ACL_CHOICES)), value))
|
||||
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':
|
||||
value = mapCollaborativeACL(myarg, value)
|
||||
for attrName, attrValue in list(COLLABORATIVE_INBOX_ATTRIBUTES.items()):
|
||||
if attrValue == 'acl':
|
||||
gs_body[attrName] = value
|
||||
else:
|
||||
gs_body[attrName] = attrValue
|
||||
return
|
||||
myarg = 'enablecollaborativeinbox'
|
||||
for (attrib, params) in list(gs_object['schemas']['Groups']['properties'].items()):
|
||||
if attrib in ['kind', 'etag', 'email']:
|
||||
continue
|
||||
@@ -8746,19 +8905,19 @@ def getGroupAttrValue(myarg, value, gs_object, gs_body, function):
|
||||
value = value.replace('\\n', '\n')
|
||||
elif attrib == 'primaryLanguage':
|
||||
value = LANGUAGE_CODES_MAP.get(value.lower(), value)
|
||||
elif COLLABORATIVE_INBOX_ATTRIBUTES.get(attrib) == 'acl':
|
||||
value = mapCollaborativeACL(myarg, value)
|
||||
elif params['description'].find(value.upper()) != -1: # ugly hack because API wants some values uppercased.
|
||||
elif attrib in GROUP_SETTINGS_LIST_ATTRIBUTES:
|
||||
value = value.upper()
|
||||
elif value.lower() in true_values:
|
||||
value = 'true'
|
||||
elif value.lower() in false_values:
|
||||
value = 'false'
|
||||
# Another ugly hack because Groups Settings API doesn't have proper enumerator values set in discovery file.
|
||||
if 'description' in params and params['description'].find('Possible values are: ') != -1:
|
||||
possible_values = params['description'][params['description'].find('Possible values are: ')+21:].split(' ')
|
||||
if value not in possible_values:
|
||||
systemErrorExit(2, 'value for %s must be one of %s. Got %s.' % (attrib, ', '.join(possible_values), value))
|
||||
possible_values = GROUP_SETTINGS_LIST_PATTERN.findall(params['description'])
|
||||
if value not in possible_values:
|
||||
systemErrorExit(2, 'value for %s must be one of %s. Got %s.' % (attrib, ', '.join(possible_values), value))
|
||||
elif attrib in GROUP_SETTINGS_BOOLEAN_ATTRIBUTES:
|
||||
value = value.lower()
|
||||
if value in true_values:
|
||||
value = 'true'
|
||||
elif value in false_values:
|
||||
value = 'false'
|
||||
else:
|
||||
systemErrorExit(2, 'value for %s must be true|false. Got %s.' % (attrib, value))
|
||||
gs_body[attrib] = value
|
||||
return
|
||||
systemErrorExit(2, '%s is not a valid argument for "gam %s group"' % (myarg, function))
|
||||
@@ -8806,12 +8965,12 @@ def doCreateGroup():
|
||||
if gs and not GroupIsAbuseOrPostmaster(body['email']):
|
||||
if gs_get_before_update:
|
||||
current_settings = callGAPI(gs.groups(), 'get',
|
||||
retry_reasons=['serviceLimit'],
|
||||
retry_reasons=['serviceLimit', 'notFound'],
|
||||
groupUniqueId=body['email'], fields='*')
|
||||
if current_settings is not None:
|
||||
gs_body = dict(list(current_settings.items()) + list(gs_body.items()))
|
||||
if gs_body:
|
||||
callGAPI(gs.groups(), 'update', retry_reasons=['serviceLimit'], groupUniqueId=body['email'], body=gs_body)
|
||||
callGAPI(gs.groups(), 'update', retry_reasons=['serviceLimit', 'notFound'], groupUniqueId=body['email'], body=gs_body)
|
||||
|
||||
def doCreateAlias():
|
||||
cd = buildGAPIObject('directory')
|
||||
@@ -9149,6 +9308,16 @@ def checkGroupExists(cd, group, i=0, count=0):
|
||||
entityUnknownWarning('Group', group, i, count)
|
||||
return None
|
||||
|
||||
def _checkMemberRoleIsSuspended(member, validRoles, isSuspended):
|
||||
if validRoles and member.get('role', ROLE_MEMBER) not in validRoles:
|
||||
return False
|
||||
if isSuspended is None:
|
||||
return True
|
||||
memberStatus = member.get('status', 'UNKNOWN')
|
||||
if not isSuspended:
|
||||
return memberStatus != 'SUSPENDED'
|
||||
return memberStatus == 'SUSPENDED'
|
||||
|
||||
UPDATE_GROUP_SUBCMDS = ['add', 'clear', 'delete', 'remove', 'sync', 'update']
|
||||
GROUP_ROLES_MAP = {
|
||||
'owner': ROLE_OWNER, 'owners': ROLE_OWNER,
|
||||
@@ -9353,12 +9522,7 @@ def doUpdateGroup():
|
||||
if not result:
|
||||
print('Group already has 0 members')
|
||||
return
|
||||
if checkSuspended is None:
|
||||
users_email = [member.get('email', member['id']) for member in result if not validRoles or member.get('role', ROLE_MEMBER) in validRoles]
|
||||
elif checkSuspended:
|
||||
users_email = [member.get('email', member['id']) for member in result if (not validRoles or member.get('role', ROLE_MEMBER) in validRoles) and member['status'] == 'SUSPENDED']
|
||||
else: # elif not checkSuspended
|
||||
users_email = [member.get('email', member['id']) for member in result if (not validRoles or member.get('role', ROLE_MEMBER) in validRoles) and member['status'] != 'SUSPENDED']
|
||||
users_email = [member.get('email', member['id']) for member in result if _checkMemberRoleIsSuspended(member, validRoles, checkSuspended)]
|
||||
if len(users_email) > 1:
|
||||
sys.stderr.write('Group: {0}, Will remove {1} {2}{3}s.\n'.format(group, len(users_email), '' if checkSuspended is None else ['Non-suspended ', 'Suspended '][checkSuspended], roles))
|
||||
for user_email in users_email:
|
||||
@@ -9528,11 +9692,21 @@ def doUpdateCros():
|
||||
|
||||
def doUpdateMobile():
|
||||
cd = buildGAPIObject('directory')
|
||||
resourceId = sys.argv[3]
|
||||
resourceIds = sys.argv[3]
|
||||
match_users = None
|
||||
doit = False
|
||||
if resourceIds[:6] == 'query:':
|
||||
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)
|
||||
else:
|
||||
devices = [{'resourceId': resourceIds, 'email': ['not set']}]
|
||||
doit = True
|
||||
i = 4
|
||||
body = {}
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
myarg = sys.argv[i].lower().replace('_', '')
|
||||
if myarg == 'action':
|
||||
body['action'] = sys.argv[i+1].lower()
|
||||
if body['action'] == 'wipe':
|
||||
@@ -9542,10 +9716,29 @@ def doUpdateMobile():
|
||||
if body['action'] not in ['admin_remote_wipe', 'admin_account_wipe', 'approve', 'block', 'cancel_remote_wipe_then_activate', 'cancel_remote_wipe_then_block']:
|
||||
systemErrorExit(2, 'action must be one of wipe, wipeaccount, approve, block, cancel_remote_wipe_then_activate, cancel_remote_wipe_then_block; got %s' % body['action'])
|
||||
i += 2
|
||||
elif myarg in ['ifusers', 'matchusers']:
|
||||
match_users = getUsersToModify(entity_type=sys.argv[i+1].lower(), entity=sys.argv[i+2])
|
||||
i += 3
|
||||
elif myarg == 'doit':
|
||||
doit = True
|
||||
i += 1
|
||||
else:
|
||||
systemErrorExit(2, '%s is not a valid argument for "gam update mobile"' % sys.argv[i])
|
||||
if body:
|
||||
callGAPI(cd.mobiledevices(), 'action', resourceId=resourceId, body=body, customerId=GC_Values[GC_CUSTOMER_ID])
|
||||
if doit:
|
||||
print('Updating %s devices' % len(devices))
|
||||
describe_as = 'Performing'
|
||||
else:
|
||||
print('Showing %s changes that would be made, not actually making changes because doit argument not specified' % len(devices))
|
||||
describe_as = 'Would perform'
|
||||
for device in devices:
|
||||
device_user = device.get('email', [''])[0]
|
||||
if match_users and device_user not in match_users:
|
||||
print('Skipping device for user %s that did not match match_users argument' % device_user)
|
||||
else:
|
||||
print('%s %s on user %s device %s' % (describe_as, body['action'], device_user, device['resourceId']))
|
||||
if doit:
|
||||
callGAPI(cd.mobiledevices(), 'action', resourceId=device['resourceId'], body=body, customerId=GC_Values[GC_CUSTOMER_ID])
|
||||
|
||||
def doDeleteMobile():
|
||||
cd = buildGAPIObject('directory')
|
||||
@@ -10469,27 +10662,42 @@ def doSiteVerifyAttempt():
|
||||
print('ERROR: %s' % str(e))
|
||||
verify_data = callGAPI(verif.webResource(), 'getToken', body=body)
|
||||
print('Method: %s' % verify_data['method'])
|
||||
print('Token: %s' % verify_data['token'])
|
||||
print('Expected Token: %s' % verify_data['token'])
|
||||
if verify_data['method'] in ['DNS_CNAME', 'DNS_TXT']:
|
||||
resolver = dns.resolver.Resolver()
|
||||
resolver.nameservers = ['8.8.8.8', '8.8.4.4']
|
||||
simplehttp = _createHttpObj()
|
||||
base_url = 'https://dns.google/resolve?'
|
||||
query_params = {}
|
||||
if verify_data['method'] == 'DNS_CNAME':
|
||||
cname_token = verify_data['token']
|
||||
cname_list = cname_token.split(' ')
|
||||
cname_subdomain = cname_list[0]
|
||||
try:
|
||||
answers = resolver.query('%s.%s' % (cname_subdomain, a_domain), 'A')
|
||||
for answer in answers:
|
||||
print('DNS Record: %s' % answer)
|
||||
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
|
||||
print('ERROR: No such domain found in DNS!')
|
||||
query_params['name'] = '%s.%s' % (cname_subdomain, a_domain)
|
||||
query_params['type'] = 'cname'
|
||||
else:
|
||||
try:
|
||||
answers = resolver.query(a_domain, 'TXT')
|
||||
for answer in answers:
|
||||
print('DNS Record: %s' % str(answer).replace('"', ''))
|
||||
except dns.resolver.NXDOMAIN:
|
||||
print('ERROR: no such domain found in DNS!')
|
||||
query_params['name'] = a_domain
|
||||
query_params['type'] = 'txt'
|
||||
full_url = base_url + urlencode(query_params)
|
||||
(_, c) = simplehttp.request(full_url, 'GET')
|
||||
result = json.loads(c.decode(UTF8))
|
||||
status = result['Status']
|
||||
if status == 0 and 'Answer' in result:
|
||||
answers = result['Answer']
|
||||
if verify_data['method'] == 'DNS_CNAME':
|
||||
answer = answers[0]['data']
|
||||
else:
|
||||
answer = 'no matching record found'
|
||||
for possible_answer in answers:
|
||||
possible_answer['data'] = possible_answer['data'].strip('"')
|
||||
if possible_answer['data'].startswith('google-site-verification'):
|
||||
answer = possible_answer['data']
|
||||
break
|
||||
else:
|
||||
print('Unrelated TXT record: %s' % possible_answer['data'])
|
||||
print('Found DNS Record: %s' % answer)
|
||||
elif status == 0:
|
||||
systemErrorExit(1, 'DNS record not found')
|
||||
else:
|
||||
systemErrorExit(status, DNS_ERROR_CODES_MAP.get(status, 'Unknown error %s' % status))
|
||||
return
|
||||
print('SUCCESS!')
|
||||
print('Verified: %s' % verify_result['site']['identifier'])
|
||||
@@ -10928,30 +11136,42 @@ def doDeleteOrg():
|
||||
print("Deleting organization %s" % name)
|
||||
callGAPI(cd.orgunits(), 'delete', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=encodeOrgUnitPath(makeOrgUnitPathRelative(name)))
|
||||
|
||||
def send_email(subject, body, recipient=None, sender=None, method='send', labels=None):
|
||||
def send_email(subject, body, recipient=None, sender=None, user=None, method='send', labels=None, msgHeaders={}, kwargs={}):
|
||||
api_body = {}
|
||||
kwargs = {}
|
||||
if not sender:
|
||||
sender = _getValueFromOAuth('email')
|
||||
userId, gmail = buildGmailGAPIObject(sender)
|
||||
default_sender = default_recipient = False
|
||||
if not user:
|
||||
user = _getValueFromOAuth('email')
|
||||
userId, gmail = buildGmailGAPIObject(user)
|
||||
resource = gmail.users().messages()
|
||||
if labels and method in ['insert', 'import']:
|
||||
if labels:
|
||||
api_body['labelIds'] = labelsToLabelIds(gmail, labels)
|
||||
elif labels:
|
||||
systemErrorExit(3, 'labels argument is only valid for importemail and insertemail')
|
||||
if not sender:
|
||||
sender = userId
|
||||
default_sender = True
|
||||
if not recipient:
|
||||
recipient = userId
|
||||
default_recipient = True
|
||||
msg = message_from_string(body)
|
||||
msg['Subject'] = subject
|
||||
msg['From'] = userId
|
||||
msg['To'] = recipient
|
||||
for header, value in msgHeaders.items():
|
||||
msg.__delitem__(header) # can remove multiple case-insensitive matching headers
|
||||
msg.add_header(header, value)
|
||||
if subject:
|
||||
msg.__delitem__('Subject')
|
||||
msg['Subject'] = subject
|
||||
if not default_sender:
|
||||
msg.__delitem__('From')
|
||||
if not msg['From']:
|
||||
msg['From'] = sender
|
||||
if not default_recipient:
|
||||
msg.__delitem__('to')
|
||||
if not msg['To']:
|
||||
msg['To'] = recipient
|
||||
api_body['raw'] = base64.urlsafe_b64encode(msg.as_bytes()).decode()
|
||||
if method == 'draft':
|
||||
resource = gmail.users().drafts()
|
||||
method = 'create'
|
||||
api_body = {'message': api_body}
|
||||
elif method in ['insert', 'import']:
|
||||
kwargs['internalDateSource'] = 'dateHeader'
|
||||
if method == 'import':
|
||||
method = 'import_'
|
||||
callGAPI(resource, method, userId=userId, body=api_body, **kwargs)
|
||||
@@ -11016,6 +11236,9 @@ def sortCSVTitles(firstTitle, titles):
|
||||
for title in restoreTitles[::-1]:
|
||||
titles.insert(0, title)
|
||||
|
||||
def QuotedArgumentList(items):
|
||||
return ' '.join([item if item and (item.find(' ') == -1) and (item.find(',') == -1) else '"'+item+'"' for item in items])
|
||||
|
||||
def writeCSVfile(csvRows, titles, list_type, todrive):
|
||||
def rowDateTimeFilterMatch(dateMode, rowDate, op, filterDate):
|
||||
if not rowDate or not isinstance(rowDate, str):
|
||||
@@ -11120,7 +11343,7 @@ and follow recommend steps to authorize GAM for Drive access.''' % (admin_email)
|
||||
mimeType = 'text/csv'
|
||||
else:
|
||||
mimeType = MIMETYPE_GA_SPREADSHEET
|
||||
body = {'description': ' '.join(sys.argv),
|
||||
body = {'description': QuotedArgumentList(sys.argv),
|
||||
'name': '%s - %s' % (GC_Values[GC_DOMAIN], list_type),
|
||||
'mimeType': mimeType}
|
||||
result = callGAPI(drive.files(), 'create', fields='webViewLink',
|
||||
@@ -11384,12 +11607,12 @@ def doPrintShowAlertFeedback():
|
||||
for feedbac in feedback:
|
||||
print(feedbac)
|
||||
|
||||
def _getValidAlertFeedbackTypes(ac):
|
||||
return [aftype for aftype in ac._rootDesc['schemas']['AlertFeedback']['properties']['type']['enum'] if aftype != 'ALERT_FEEDBACK_TYPE_UNSPECIFIED']
|
||||
def _getEnumValuesMinusUnspecified(values):
|
||||
return [a_type for a_type in values if '_UNSPECIFIED' not in a_type]
|
||||
|
||||
def doCreateAlertFeedback():
|
||||
_, ac = buildAlertCenterGAPIObject(_getValueFromOAuth('email'))
|
||||
valid_types = _getValidAlertFeedbackTypes(ac)
|
||||
valid_types = _getEnumValuesMinusUnspecified(ac._rootDesc['schemas']['AlertFeedback']['properties']['type']['enum'])
|
||||
alertId = sys.argv[3]
|
||||
body = {'type': sys.argv[4].upper()}
|
||||
if body['type'] not in valid_types:
|
||||
@@ -11416,11 +11639,14 @@ GROUP_ARGUMENT_TO_PROPERTY_TITLE_MAP = {
|
||||
|
||||
GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP = {
|
||||
'allowexternalmembers': 'allowExternalMembers',
|
||||
'allowgooglecommunication': 'allowGoogleCommunication',
|
||||
'allowwebposting': 'allowWebPosting',
|
||||
'archiveonly': 'archiveOnly',
|
||||
'customfootertext': 'customFooterText',
|
||||
'customreplyto': 'customReplyTo',
|
||||
'defaultmessagedenynotificationtext': 'defaultMessageDenyNotificationText',
|
||||
'enablecollaborativeinbox': 'enableCollaborativeInbox',
|
||||
'favoriterepliesontop': 'favoriteRepliesOnTop',
|
||||
'gal': 'includeInGlobalAddressList',
|
||||
'includecustomfooter': 'includeCustomFooter',
|
||||
'includeinglobaladdresslist': 'includeInGlobalAddressList',
|
||||
@@ -11433,16 +11659,33 @@ GROUP_ATTRIBUTES_ARGUMENT_TO_PROPERTY_MAP = {
|
||||
'showingroupdirectory': 'showInGroupDirectory',
|
||||
'spammoderationlevel': 'spamModerationLevel',
|
||||
'whocanadd': 'whoCanAdd',
|
||||
'whocanapprovemembers': 'whoCanApproveMembers',
|
||||
'whocanapprovemessages': 'whoCanApproveMessages',
|
||||
'whocanassigntopics': 'whoCanAssignTopics',
|
||||
'whocanassistcontent': 'whoCanAssistContent',
|
||||
'whocanbanusers': 'whoCanBanUsers',
|
||||
'whocancontactowner': 'whoCanContactOwner',
|
||||
'whocandeleteanypost': 'whoCanDeleteAnyPost',
|
||||
'whocandeletetopics': 'whoCanDeleteTopics',
|
||||
'whocandiscovergroup': 'whoCanDiscoverGroup',
|
||||
'whocanenterfreeformtags': 'whoCanEnterFreeFormTags',
|
||||
'whocanhideabuse': 'whoCanHideAbuse',
|
||||
'whocaninvite': 'whoCanInvite',
|
||||
'whocanjoin': 'whoCanJoin',
|
||||
'whocanleavegroup': 'whoCanLeaveGroup',
|
||||
'whocanlocktopics': 'whoCanLockTopics',
|
||||
'whocanmaketopicssticky': 'whoCanMakeTopicsSticky',
|
||||
'whocanmarkduplicate': 'whoCanMarkDuplicate',
|
||||
'whocanmarkfavoritereplyonanytopic': 'whoCanMarkFavoriteReplyOnAnyTopic',
|
||||
'whocanmarkfavoritereplyonowntopic': 'whoCanMarkFavoriteReplyOnOwnTopic',
|
||||
'whocanmarknoresponseneeded': 'whoCanMarkNoResponseNeeded',
|
||||
'whocanmoderatecontent': 'whoCanModerateContent',
|
||||
'whocanmoderatemembers': 'whoCanModerateMembers',
|
||||
'whocanmodifymembers': 'whoCanModifyMembers',
|
||||
'whocanmodifytagsandcategories': 'whoCanModifyTagsAndCategories',
|
||||
'whocanmovetopicsin': 'whoCanMoveTopicsIn',
|
||||
'whocanmovetopicsout': 'whoCanMoveTopicsOut',
|
||||
'whocanpostannouncements': 'whoCanPostAnnouncements',
|
||||
'whocanpostmessage': 'whoCanPostMessage',
|
||||
'whocantaketopics': 'whoCanTakeTopics',
|
||||
'whocanunassigntopic': 'whoCanUnassignTopic',
|
||||
@@ -11868,8 +12111,7 @@ def doPrintGroupMembers():
|
||||
soft_errors=True,
|
||||
groupKey=group_email, roles=listRoles, fields=listFields, maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
|
||||
for member in group_members:
|
||||
if ((validRoles and member.get('role', ROLE_MEMBER) not in validRoles) or
|
||||
(checkSuspended is not None and ((not checkSuspended and member['status'] == 'SUSPENDED') or (checkSuspended and member['status'] != 'SUSPENDED')))):
|
||||
if not _checkMemberRoleIsSuspended(member, validRoles, checkSuspended):
|
||||
continue
|
||||
for title in member:
|
||||
if title not in titles:
|
||||
@@ -12671,12 +12913,16 @@ def doPrintResourceCalendars():
|
||||
fieldsTitles = {}
|
||||
titles = []
|
||||
csvRows = []
|
||||
query = None
|
||||
i = 3
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower()
|
||||
if myarg == 'todrive':
|
||||
todrive = True
|
||||
i += 1
|
||||
elif myarg == 'query':
|
||||
query = sys.argv[i+1]
|
||||
i += 2
|
||||
elif myarg == 'allfields':
|
||||
fieldsList = []
|
||||
fieldsTitles = {}
|
||||
@@ -12699,7 +12945,8 @@ def doPrintResourceCalendars():
|
||||
page_message = 'Got %%total_items%% Resource Calendars: %%first_item%% - %%last_item%%\n'
|
||||
resources = callGAPIpages(cd.resources().calendars(), 'list', 'items',
|
||||
page_message=page_message, message_attribute='resourceId',
|
||||
customer=GC_Values[GC_CUSTOMER_ID], fields=fields)
|
||||
customer=GC_Values[GC_CUSTOMER_ID], query=query,
|
||||
fields=fields)
|
||||
for resource in resources:
|
||||
if 'featureInstances' in resource:
|
||||
resource['featureInstances'] = ','.join([a_feature['feature']['name'] for a_feature in resource.pop('featureInstances')])
|
||||
@@ -12761,9 +13008,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=No
|
||||
groupKey=group, roles=listRoles, fields=listFields, maxResults=GC_Values[GC_MEMBER_MAX_RESULTS])
|
||||
users = []
|
||||
for member in members:
|
||||
if (((not groupUserMembersOnly) or (member['type'] == 'USER')) and
|
||||
(not validRoles or member.get('role', ROLE_MEMBER) in validRoles) and
|
||||
(checkSuspended is None or (not checkSuspended and member['status'] != 'SUSPENDED') or (checkSuspended and member['status'] == 'SUSPENDED'))):
|
||||
if ((not groupUserMembersOnly) or (member['type'] == 'USER')) and _checkMemberRoleIsSuspended(member, validRoles, checkSuspended):
|
||||
users.append(member.get('email', member['id']))
|
||||
elif entity_type in ['ou', 'org', 'ou_ns', 'org_ns', 'ou_susp', 'org_susp',]:
|
||||
if entity_type in ['ou_ns', 'org_ns']:
|
||||
@@ -13841,6 +14086,8 @@ def ProcessGAMCommand(args):
|
||||
doCreateFeature()
|
||||
elif argument in ['alertfeedback']:
|
||||
doCreateAlertFeedback()
|
||||
elif argument in ['gcpfolder']:
|
||||
createGCPFolder()
|
||||
else:
|
||||
systemErrorExit(2, '%s is not a valid argument for "gam create"' % argument)
|
||||
sys.exit(0)
|
||||
@@ -14198,6 +14445,8 @@ def ProcessGAMCommand(args):
|
||||
argument = sys.argv[2].lower()
|
||||
if argument in ['export', 'vaultexport']:
|
||||
doDownloadVaultExport()
|
||||
elif argument in ['storagebucket']:
|
||||
doDownloadCloudStorageBucket()
|
||||
else:
|
||||
systemErrorExit(2, '%s is not a valid argument for "gam download"' % argument)
|
||||
sys.exit(0)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
python-dateutil
|
||||
dnspython
|
||||
google-api-python-client
|
||||
google-api-python-client>=1.7.10
|
||||
google-auth
|
||||
google-auth-httplib2
|
||||
google-auth-oauthlib==0.4.0
|
||||
|
||||
@@ -10,7 +10,7 @@ else
|
||||
echo "RUNNING: apt update..."
|
||||
sudo apt-get -qq --yes update > /dev/null
|
||||
echo "RUNNING: apt dist-upgrade..."
|
||||
sudo apt-get -qq --yes dist-upgrade > /dev/null
|
||||
# sudo apt-get -qq --yes dist-upgrade > /dev/null
|
||||
echo "Installing build tools..."
|
||||
sudo apt-get -qq --yes install build-essential
|
||||
|
||||
@@ -47,28 +47,38 @@ else
|
||||
cd ~/pybuild
|
||||
# Compile latest Python
|
||||
if [ ! -d Python-$BUILD_PYTHON_VERSION ]; then
|
||||
wget --quiet https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
echo "Downloading Python $BUILD_PYTHON_VERSION..."
|
||||
curl -O https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
echo "Extracting Python..."
|
||||
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"
|
||||
|
||||
if [ ! -e Makefile ]; then
|
||||
echo "running configure with safe and unsafe"
|
||||
./configure $safe_flags $unsafe_flags > /dev/null
|
||||
fi
|
||||
make -j$cpucount -s
|
||||
timeout 1800 make -j$cpucount -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
|
||||
@@ -100,11 +110,11 @@ else
|
||||
fi
|
||||
$pip install git+https://github.com/JonathonReinhart/staticx.git@master
|
||||
fi
|
||||
$pip install pyinstaller
|
||||
cd $whereibelong
|
||||
fi
|
||||
|
||||
echo "Upgrading pip packages..."
|
||||
$pip freeze > upgrades.txt
|
||||
$pip install --upgrade -r upgrades.txt
|
||||
$pip install -r src/requirements.txt
|
||||
$pip install --upgrade -r src/requirements.txt
|
||||
$pip install --upgrade pyinstaller
|
||||
|
||||
@@ -3,7 +3,7 @@ if [ "$VMTYPE" == "test" ]; then
|
||||
export gam="$python gam.py"
|
||||
export gampath=$(readlink -e .)
|
||||
else
|
||||
$python -OO -m PyInstaller --clean --debug -F --distpath=gam $GAMOS-gam.spec
|
||||
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath=gam $GAMOS-gam.spec
|
||||
export gam="gam/gam"
|
||||
export gampath=$(readlink -e gam)
|
||||
export GAMVERSION=`$gam version simple`
|
||||
|
||||
@@ -1,54 +1,63 @@
|
||||
mypath=$HOME
|
||||
whereibelong=$(pwd)
|
||||
echo "Brew installing xz..."
|
||||
brew install xz > /dev/null
|
||||
#echo "Brew installing xz..."
|
||||
#brew install xz > /dev/null
|
||||
|
||||
cd ~/pybuild
|
||||
# Compile latest OpenSSL
|
||||
if [ ! -d openssl-$BUILD_OPENSSL_VERSION ]; then
|
||||
wget --quiet https://www.openssl.org/source/openssl-$BUILD_OPENSSL_VERSION.tar.gz
|
||||
echo "Extracting OpenSSL..."
|
||||
tar xf openssl-$BUILD_OPENSSL_VERSION.tar.gz
|
||||
|
||||
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
|
||||
cd openssl-$BUILD_OPENSSL_VERSION
|
||||
echo "Compiling OpenSSL $BUILD_OPENSSL_VERSION..."
|
||||
./config shared --prefix=$mypath/ssl
|
||||
echo "Running make for OpenSSL..."
|
||||
make -j$cpucount -s
|
||||
echo "Running make install for OpenSSL..."
|
||||
make install > /dev/null
|
||||
export LD_LIBRARY_PATH=~/ssl/lib
|
||||
cd ~/pybuild
|
||||
sudo installer -pkg python-$BUILD_PYTHON_VERSION-macosx10.9.pkg -target /
|
||||
export python=python3
|
||||
export pip=pip3
|
||||
|
||||
# Compile latest OpenSSL
|
||||
#if [ ! -d openssl-$BUILD_OPENSSL_VERSION ]; then
|
||||
# wget --quiet https://www.openssl.org/source/openssl-$BUILD_OPENSSL_VERSION.tar.gz
|
||||
# echo "Extracting OpenSSL..."
|
||||
# tar xf openssl-$BUILD_OPENSSL_VERSION.tar.gz
|
||||
#fi
|
||||
#cd openssl-$BUILD_OPENSSL_VERSION
|
||||
#echo "Compiling OpenSSL $BUILD_OPENSSL_VERSION..."
|
||||
#./config shared --prefix=$mypath/ssl
|
||||
#echo "Running make for OpenSSL..."
|
||||
#make -j$cpucount -s
|
||||
#echo "Running make install for OpenSSL..."
|
||||
#make install > /dev/null
|
||||
#export LD_LIBRARY_PATH=~/ssl/lib
|
||||
#cd ~/pybuild
|
||||
|
||||
# Compile latest Python
|
||||
if [ ! -d Python-$BUILD_PYTHON_VERSION ]; then
|
||||
wget --quiet https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
echo "Extracting Python..."
|
||||
tar xf Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
fi
|
||||
cd Python-$BUILD_PYTHON_VERSION
|
||||
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"
|
||||
if [ ! -e Makefile ]; then
|
||||
./configure $safe_flags $unsafe_flags > /dev/null
|
||||
fi
|
||||
make -j$cpucount -s
|
||||
RESULT=$?
|
||||
echo "Make Python exited with $RESULT"
|
||||
if [ $RESULT != 0 ]; then
|
||||
echo "Trying Python make again without unsafe flags..."
|
||||
make clean
|
||||
./configure $safe_flags > /dev/null
|
||||
make -j$cpucount -s
|
||||
fi
|
||||
echo "Installing Python..."
|
||||
make install > /dev/null
|
||||
cd ~
|
||||
#if [ ! -d Python-$BUILD_PYTHON_VERSION ]; then
|
||||
# wget --quiet https://www.python.org/ftp/python/$BUILD_PYTHON_VERSION/Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
# echo "Extracting Python..."
|
||||
# tar xf Python-$BUILD_PYTHON_VERSION.tar.xz
|
||||
#fi
|
||||
#cd Python-$BUILD_PYTHON_VERSION
|
||||
#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"
|
||||
#if [ ! -e Makefile ]; then
|
||||
# ./configure $safe_flags $unsafe_flags > /dev/null
|
||||
#fi
|
||||
#make -j$cpucount -s
|
||||
#RESULT=$?
|
||||
#echo "Make Python exited with $RESULT"
|
||||
#if [ $RESULT != 0 ]; then
|
||||
# echo "Trying Python make again without unsafe flags..."
|
||||
# make clean
|
||||
# ./configure $safe_flags > /dev/null
|
||||
# make -j$cpucount -s
|
||||
#fi
|
||||
#echo "Installing Python..."
|
||||
#make install > /dev/null
|
||||
#cd ~
|
||||
|
||||
#export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
|
||||
#python=~/python/bin/python3
|
||||
#pip=~/python/bin/pip3
|
||||
|
||||
export LD_LIBRARY_PATH=~/ssl/lib:~/python/lib
|
||||
python=~/python/bin/python3
|
||||
pip=~/python/bin/pip3
|
||||
|
||||
$python -V
|
||||
|
||||
@@ -58,5 +67,5 @@ export PATH=/usr/local/opt/python/libexec/bin:$PATH
|
||||
$pip install --upgrade pip
|
||||
$pip freeze > upgrades.txt
|
||||
$pip install --upgrade -r upgrades.txt
|
||||
$pip install -r src/requirements.txt
|
||||
$pip install pyinstaller
|
||||
$pip install --upgrade -r src/requirements.txt
|
||||
$pip install --upgrade pyinstaller
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
cd src
|
||||
$python -OO -m PyInstaller --clean --debug -F --distpath=gam $GAMOS-gam.spec
|
||||
$python -OO -m PyInstaller --clean --noupx --strip -F --distpath=gam $GAMOS-gam.spec
|
||||
export gam="gam/gam"
|
||||
export gampath=gam
|
||||
$gam version extended
|
||||
|
||||
@@ -2,26 +2,44 @@ 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 -r src/requirements.txt
|
||||
pip install pyinstaller
|
||||
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-32bit/*
|
||||
/c/python37/python ./waf all --target-arch=32bit
|
||||
echo "bootloader after:"
|
||||
md5sum ../PyInstaller/bootloader/Windows-32bit/*
|
||||
echo "PATH: $PATH"
|
||||
cd ..
|
||||
/c/python37/python setup.py install
|
||||
cd $mypath
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
cd src
|
||||
pyinstaller --clean -F --distpath=gam $GAMOS-gam.spec
|
||||
pyinstaller --clean --noupx -F --distpath=gam $GAMOS-gam.spec
|
||||
export gam="gam/gam"
|
||||
export gampath=$(readlink -e gam)
|
||||
$gam version extended
|
||||
|
||||
@@ -2,26 +2,43 @@ 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
|
||||
#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 -r src/requirements.txt
|
||||
pip install pyinstaller
|
||||
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/*
|
||||
/c/python37/python ./waf all --target-arch=64bit
|
||||
echo "bootloader after:"
|
||||
md5sum ../PyInstaller/bootloader/Windows-64bit/*
|
||||
echo "PATH: $PATH"
|
||||
cd ..
|
||||
/c/python37/python setup.py install
|
||||
cd $mypath
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
cd src
|
||||
pyinstaller --clean -F --distpath=gam $GAMOS-gam.spec
|
||||
pyinstaller --clean --noupx -F --distpath=gam $GAMOS-gam.spec
|
||||
export gam="gam/gam"
|
||||
export gampath=$(readlink -e gam)
|
||||
$gam version
|
||||
|
||||
134
src/var.py
134
src/var.py
@@ -6,15 +6,15 @@ import platform
|
||||
import re
|
||||
|
||||
gam_author = 'Jay Lee <jay0lee@gmail.com>'
|
||||
gam_version = '4.88'
|
||||
gam_version = '4.92'
|
||||
gam_license = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
GAM_URL = 'https://git.io/gam'
|
||||
GAM_INFO = 'GAM {0} - {1} / {2} / Python {3}.{4}.{5} {6} / {7} {8} /'.format(gam_version, GAM_URL,
|
||||
gam_author,
|
||||
sys.version_info[0], sys.version_info[1],
|
||||
sys.version_info[2], sys.version_info[3],
|
||||
platform.platform(), platform.machine())
|
||||
gam_author,
|
||||
sys.version_info[0], sys.version_info[1],
|
||||
sys.version_info[2], sys.version_info[3],
|
||||
platform.platform(), platform.machine())
|
||||
|
||||
GAM_RELEASES = 'https://github.com/jay0lee/GAM/releases'
|
||||
GAM_WIKI = 'https://github.com/jay0lee/GAM/wiki'
|
||||
@@ -157,13 +157,13 @@ API_VER_MAPPING = {
|
||||
API_SCOPE_MAPPING = {
|
||||
'alertcenter': ['https://www.googleapis.com/auth/apps.alerts',],
|
||||
'appsactivity': ['https://www.googleapis.com/auth/activity',
|
||||
'https://www.googleapis.com/auth/drive',],
|
||||
'https://www.googleapis.com/auth/drive',],
|
||||
'calendar': ['https://www.googleapis.com/auth/calendar',],
|
||||
'drive': ['https://www.googleapis.com/auth/drive',],
|
||||
'drive3': ['https://www.googleapis.com/auth/drive',],
|
||||
'gmail': ['https://mail.google.com/',
|
||||
'https://www.googleapis.com/auth/gmail.settings.basic',
|
||||
'https://www.googleapis.com/auth/gmail.settings.sharing',],
|
||||
'https://www.googleapis.com/auth/gmail.settings.basic',
|
||||
'https://www.googleapis.com/auth/gmail.settings.sharing',],
|
||||
'sheets': ['https://www.googleapis.com/auth/spreadsheets',],
|
||||
}
|
||||
|
||||
@@ -403,7 +403,7 @@ DOCUMENT_FORMATS_MAP = {
|
||||
'mht': [{'mime': 'message/rfc822', 'ext': 'mht'}],
|
||||
'odp': [{'mime': 'application/vnd.oasis.opendocument.presentation', 'ext': '.odp'}],
|
||||
'ods': [{'mime': 'application/x-vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
|
||||
{'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'}],
|
||||
{'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'}],
|
||||
'odt': [{'mime': 'application/vnd.oasis.opendocument.text', 'ext': '.odt'}],
|
||||
'pdf': [{'mime': 'application/pdf', 'ext': '.pdf'}],
|
||||
'png': [{'mime': 'image/png', 'ext': '.png'}],
|
||||
@@ -414,7 +414,7 @@ DOCUMENT_FORMATS_MAP = {
|
||||
'rtf': [{'mime': 'application/rtf', 'ext': '.rtf'}],
|
||||
'svg': [{'mime': 'image/svg+xml', 'ext': '.svg'}],
|
||||
'tsv': [{'mime': 'text/tab-separated-values', 'ext': '.tsv'},
|
||||
{'mime': 'text/tsv', 'ext': '.tsv'}],
|
||||
{'mime': 'text/tsv', 'ext': '.tsv'}],
|
||||
'txt': [{'mime': 'text/plain', 'ext': '.txt'}],
|
||||
'xls': [{'mime': 'application/vnd.ms-excel', 'ext': '.xls'}],
|
||||
'xlt': [{'mime': 'application/vnd.ms-excel', 'ext': '.xlt'}],
|
||||
@@ -425,9 +425,21 @@ DOCUMENT_FORMATS_MAP = {
|
||||
'microsoft': _MICROSOFT_FORMATS_LIST,
|
||||
'micro$oft': _MICROSOFT_FORMATS_LIST,
|
||||
'openoffice': [{'mime': 'application/vnd.oasis.opendocument.presentation', 'ext': '.odp'},
|
||||
{'mime': 'application/x-vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
|
||||
{'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
|
||||
{'mime': 'application/vnd.oasis.opendocument.text', 'ext': '.odt'}],
|
||||
{'mime': 'application/x-vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
|
||||
{'mime': 'application/vnd.oasis.opendocument.spreadsheet', 'ext': '.ods'},
|
||||
{'mime': 'application/vnd.oasis.opendocument.text', 'ext': '.odt'}],
|
||||
}
|
||||
|
||||
DNS_ERROR_CODES_MAP = {
|
||||
1: 'DNS Query Format Error',
|
||||
2: 'Server failed to complete the DNS request',
|
||||
3: 'Domain name does not exist',
|
||||
4: 'Function not implemented',
|
||||
5: 'The server refused to answer for the query',
|
||||
6: 'Name that should not exist, does exist',
|
||||
7: 'RRset that should not exist, does exist',
|
||||
8: 'Server not authoritative for the zone',
|
||||
9: 'Name not in zone'
|
||||
}
|
||||
|
||||
EMAILSETTINGS_OLD_NEW_OLD_FORWARD_ACTION_MAP = {
|
||||
@@ -600,28 +612,76 @@ CROS_END_ARGUMENTS = ['end', 'enddate']
|
||||
CROS_TPM_VULN_VERSIONS = ['41f', '420', '628', '8520',]
|
||||
CROS_TPM_FIXED_VERSIONS = ['422', '62b', '8521',]
|
||||
|
||||
COLLABORATIVE_ACL_CHOICES = {
|
||||
'members': 'ALL_MEMBERS',
|
||||
'managersonly': 'MANAGERS_ONLY',
|
||||
'managers': 'OWNERS_AND_MANAGERS',
|
||||
'owners': 'OWNERS_ONLY',
|
||||
'none': 'NONE',
|
||||
}
|
||||
COLLABORATIVE_INBOX_ATTRIBUTES = [
|
||||
'whoCanAddReferences',
|
||||
'whoCanAssignTopics',
|
||||
'whoCanEnterFreeFormTags',
|
||||
'whoCanMarkDuplicate',
|
||||
'whoCanMarkFavoriteReplyOnAnyTopic',
|
||||
'whoCanMarkFavoriteReplyOnOwnTopic',
|
||||
'whoCanMarkNoResponseNeeded',
|
||||
'whoCanModifyTagsAndCategories',
|
||||
'whoCanTakeTopics',
|
||||
'whoCanUnassignTopic',
|
||||
'whoCanUnmarkFavoriteReplyOnAnyTopic',
|
||||
'favoriteRepliesOnTop',
|
||||
]
|
||||
|
||||
COLLABORATIVE_INBOX_ATTRIBUTES = {
|
||||
'whoCanAddReferences': 'acl',
|
||||
'whoCanAssignTopics': 'acl',
|
||||
'whoCanEnterFreeFormTags': 'acl',
|
||||
'whoCanMarkDuplicate': 'acl',
|
||||
'whoCanMarkFavoriteReplyOnAnyTopic': 'acl',
|
||||
'whoCanMarkFavoriteReplyOnOwnTopic': 'acl',
|
||||
'whoCanMarkNoResponseNeeded': 'acl',
|
||||
'whoCanModifyTagsAndCategories': 'acl',
|
||||
'whoCanTakeTopics': 'acl',
|
||||
'whoCanUnassignTopic': 'acl',
|
||||
'whoCanUnmarkFavoriteReplyOnAnyTopic': 'acl',
|
||||
'favoriteRepliesOnTop': True,
|
||||
}
|
||||
GROUP_SETTINGS_LIST_ATTRIBUTES = set([
|
||||
# ACL choices
|
||||
'whoCanAdd',
|
||||
'whoCanApproveMembers',
|
||||
'whoCanApproveMessages',
|
||||
'whoCanAssignTopics',
|
||||
'whoCanAssistContent',
|
||||
'whoCanBanUsers',
|
||||
'whoCanContactOwner',
|
||||
'whoCanDeleteAnyPost',
|
||||
'whoCanDeleteTopics',
|
||||
'whoCanDiscoverGroup',
|
||||
'whoCanEnterFreeFormTags',
|
||||
'whoCanHideAbuse',
|
||||
'whoCanInvite',
|
||||
'whoCanJoin',
|
||||
'whoCanLeaveGroup',
|
||||
'whoCanLockTopics',
|
||||
'whoCanMakeTopicsSticky',
|
||||
'whoCanMarkDuplicate',
|
||||
'whoCanMarkFavoriteReplyOnAnyTopic',
|
||||
'whoCanMarkFavoriteReplyOnOwnTopic',
|
||||
'whoCanMarkNoResponseNeeded',
|
||||
'whoCanModerateContent',
|
||||
'whoCanModerateMembers',
|
||||
'whoCanModifyMembers',
|
||||
'whoCanModifyTagsAndCategories',
|
||||
'whoCanMoveTopicsIn',
|
||||
'whoCanMoveTopicsOut',
|
||||
'whoCanPostAnnouncements',
|
||||
'whoCanPostMessage',
|
||||
'whoCanTakeTopics',
|
||||
'whoCanUnassignTopic',
|
||||
'whoCanUnmarkFavoriteReplyOnAnyTopic',
|
||||
'whoCanViewGroup',
|
||||
'whoCanViewMembership',
|
||||
# Miscellaneous hoices
|
||||
'messageModerationLevel',
|
||||
'replyTo',
|
||||
'spamModerationLevel',
|
||||
])
|
||||
GROUP_SETTINGS_BOOLEAN_ATTRIBUTES = set([
|
||||
'allowExternalMembers',
|
||||
'allowGoogleCommunication',
|
||||
'allowWebPosting',
|
||||
'archiveOnly',
|
||||
'enableCollaborativeInbox',
|
||||
'favoriteRepliesOnTop',
|
||||
'includeCustomFooter',
|
||||
'includeInGlobalAddressList',
|
||||
'isArchived',
|
||||
'membersCanPostAsTheGroup',
|
||||
'sendMessageDenyNotification',
|
||||
'showInGroupDirectory',
|
||||
])
|
||||
|
||||
#
|
||||
# Global variables
|
||||
@@ -733,6 +793,8 @@ 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
|
||||
@@ -777,7 +839,7 @@ GC_Defaults = {
|
||||
GC_CUSTOMER_ID: MY_CUSTOMER,
|
||||
GC_DEBUG_LEVEL: 0,
|
||||
GC_DECODED_ID_TOKEN: '',
|
||||
GC_DEVICE_MAX_RESULTS: 500,
|
||||
GC_DEVICE_MAX_RESULTS: 100,
|
||||
GC_DOMAIN: '',
|
||||
GC_DRIVE_DIR: '',
|
||||
GC_DRIVE_MAX_RESULTS: 1000,
|
||||
@@ -786,6 +848,7 @@ GC_Defaults = {
|
||||
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: '',
|
||||
@@ -837,6 +900,7 @@ GC_VAR_INFO = {
|
||||
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},
|
||||
|
||||
Reference in New Issue
Block a user