mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-04 22:31:38 +00:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6b6824ee1 | ||
|
|
2902fc8931 | ||
|
|
1a6ec398b2 | ||
|
|
7d849e0cc0 | ||
|
|
2958bd9f86 | ||
|
|
15d93c9e5d | ||
|
|
082c34b453 | ||
|
|
32e7932050 | ||
|
|
6becd08f3c | ||
|
|
26fbf9c524 | ||
|
|
d9124f3ffa | ||
|
|
aa1db89bd3 | ||
|
|
ff3a8644ec | ||
|
|
4721469b1d | ||
|
|
c4a3d29964 | ||
|
|
1454526e65 | ||
|
|
2c0026512d | ||
|
|
3c85da292e | ||
|
|
c7b5251b03 | ||
|
|
5307a560bd | ||
|
|
059e6a1813 | ||
|
|
395a561b8c | ||
|
|
6c3a744ed3 | ||
|
|
907126d642 | ||
|
|
9e4506141e | ||
|
|
5deac72484 | ||
|
|
9def6e6d73 | ||
|
|
fefe9de384 | ||
|
|
f8341be9ea | ||
|
|
3c50f464cc | ||
|
|
6961a0e1b3 | ||
|
|
3fa6cde6b0 | ||
|
|
4129e05f5e | ||
|
|
42137297a1 | ||
|
|
bc64e9a67c | ||
|
|
e1ec8b8649 | ||
|
|
b9ec06807b | ||
|
|
e3d826cdb3 | ||
|
|
4306dba9f1 | ||
|
|
cf397c228c | ||
|
|
a2a6719333 | ||
|
|
7cef626a6f | ||
|
|
be44ae4322 | ||
|
|
4c00b54ad4 | ||
|
|
8508ee4afa | ||
|
|
17b2c4091d | ||
|
|
925f4532bc | ||
|
|
786bbe5609 | ||
|
|
6be52c8b3c | ||
|
|
2c2046a784 | ||
|
|
20de452685 | ||
|
|
df603937ee | ||
|
|
315a1db144 | ||
|
|
968c096a99 | ||
|
|
6703519d36 | ||
|
|
9dd8696c1e | ||
|
|
df7c12b737 | ||
|
|
7cfba0ada1 | ||
|
|
2ee5109424 | ||
|
|
721f787f0f | ||
|
|
bc62f7a9f6 |
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"kind": "discovery#restDescription",
|
||||
"discoveryVersion": "v1",
|
||||
"id": "email-audit:v1",
|
||||
"name": "email-audit",
|
||||
"version": "v1",
|
||||
"revision": "20130823",
|
||||
"title": "Email Audit API",
|
||||
"description": "Lets you perform Google Apps email audits",
|
||||
"ownerDomain": "google.com",
|
||||
"ownerName": "Google",
|
||||
"icons": {
|
||||
"x16": "http://www.google.com/images/icons/product/search-16.gif",
|
||||
"x32": "http://www.google.com/images/icons/product/search-32.gif"
|
||||
},
|
||||
"documentationLink": "https://developers.google.com/admin-sdk/email-audit",
|
||||
"protocol": "rest",
|
||||
"baseUrl": "https://apps-apis.google.com/",
|
||||
"rootUrl": "https://apps-apis.google.com/",
|
||||
"servicePath": "/a/feeds/compliance/audit/",
|
||||
"auth": {
|
||||
"oauth2": {
|
||||
"scopes": {
|
||||
"https://apps-apis.google.com/a/feeds/compliance/audit/": {
|
||||
"description": "Manage email audits"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
},
|
||||
"resources": {
|
||||
}
|
||||
}
|
||||
280
src/gam-install.sh
Executable file
280
src/gam-install.sh
Executable file
@@ -0,0 +1,280 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
usage()
|
||||
{
|
||||
cat << EOF
|
||||
GAM installation script.
|
||||
|
||||
OPTIONS:
|
||||
-h show help.
|
||||
-d Directory where gam folder will be installed. Default is \$HOME/bin/
|
||||
-a Architecture to install (i386, x86_64, arm). Default is to detect your arch with "uname -m".
|
||||
-o OS we are running (linux, macos). Default is to detect your OS with "uname -s".
|
||||
-p Profile update (true, false). Should script add gam command to environment. Default is true.
|
||||
-u Admin user email address to use with GAM. Default is to prompt.
|
||||
-r Regular user email address. Used to test service account access to user data. Default is to prompt.
|
||||
-v Version to install (latest, prerelease, draft, 3.8, etc). Default is latest.
|
||||
EOF
|
||||
}
|
||||
|
||||
target_dir="$HOME/bin"
|
||||
gamarch=$(uname -m)
|
||||
gamos=$(uname -s)
|
||||
update_profile=true
|
||||
gamversion="latest"
|
||||
adminuser=""
|
||||
regularuser=""
|
||||
while getopts "hd:a:o:p:u:r:v:" OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
h) usage; exit;;
|
||||
d) target_dir=$OPTARG;;
|
||||
a) gamarch=$OPTARG;;
|
||||
o) gamos=$OPTARG;;
|
||||
p) update_profile=$OPTARG;;
|
||||
u) adminuser=$OPTARG;;
|
||||
r) regularuser=$OPTARG;;
|
||||
v) gamversion=$OPTARG;;
|
||||
?) usage; exit;;
|
||||
esac
|
||||
done
|
||||
|
||||
update_profile() {
|
||||
[ -f "$1" ] || return 1
|
||||
|
||||
grep -F "$alias_line" "$1" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo_yellow "Adding gam alias to profile file $1."
|
||||
echo -e "\n$alias_line" >> "$1"
|
||||
else
|
||||
echo_yellow "gam alias already exists in profile file $1. Skipping add."
|
||||
fi
|
||||
}
|
||||
|
||||
echo_red()
|
||||
{
|
||||
echo -e "\x1B[1;31m$1"
|
||||
echo -e '\x1B[0m'
|
||||
}
|
||||
|
||||
echo_green()
|
||||
{
|
||||
echo -e "\x1B[1;32m$1"
|
||||
echo -e '\x1B[0m'
|
||||
}
|
||||
|
||||
echo_yellow()
|
||||
{
|
||||
echo -e "\x1B[1;33m$1"
|
||||
echo -e '\x1B[0m'
|
||||
}
|
||||
|
||||
case $gamos in
|
||||
[lL]inux)
|
||||
gamos="linux"
|
||||
case $gamarch in
|
||||
x86_64) gamfile="linux-x86_64.tar.xz";;
|
||||
i?86) gamfile="linux-i686.tar.xz";;
|
||||
arm*) gamfile="linux-armv7l.tar.xz";;
|
||||
*)
|
||||
echo_red "ERROR: this installer currently only supports i386, x86_64 and arm Linux. Looks like you're running on $gamarch. Exiting."
|
||||
exit
|
||||
esac
|
||||
;;
|
||||
[Mm]ac[Oo][sS]|[Dd]arwin)
|
||||
osver=$(sw_vers -productVersion | awk -F'.' '{print $2}')
|
||||
if (( $osver < 10 )); then
|
||||
echo_red "ERROR: GAM currently requires MacOS 10.10 or newer. You are running MacOS 10.$osver. Please upgrade."
|
||||
exit
|
||||
else
|
||||
echo_green "Good, you're running MacOS 10.$osver..."
|
||||
fi
|
||||
gamos="macos"
|
||||
gamfile="macos.tar.xz"
|
||||
;;
|
||||
*)
|
||||
echo_red "Sorry, this installer currently only supports Linux and MacOS. Looks like you're runnning on $gamos. Exiting."
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$gamversion" == "latest" -o "$gamversion" == "prerelease" -o "$gamversion" == "draft" ]; then
|
||||
release_url="https://api.github.com/repos/jay0lee/GAM/releases"
|
||||
else
|
||||
release_url="https://api.github.com/repos/jay0lee/GAM/releases/tags/v$gamversion"
|
||||
fi
|
||||
|
||||
echo_yellow "Checking GitHub URL $release_url for $gamversion GAM release..."
|
||||
release_json=$(curl -s $release_url 2>&1 /dev/null)
|
||||
|
||||
echo_yellow "Getting file and download URL..."
|
||||
# Python is sadly the nearest to universal way to safely handle JSON with Bash
|
||||
# At least this code should be compatible with just about any Python version ever
|
||||
# unlike GAM itself. If some users don't have Python we can try grep / sed / etc
|
||||
# but that gets really ugly
|
||||
pycode="import json
|
||||
import sys
|
||||
|
||||
attrib = sys.argv[1]
|
||||
gamversion = sys.argv[2]
|
||||
|
||||
release = json.load(sys.stdin)
|
||||
if type(release) is list:
|
||||
for a_release in release:
|
||||
if a_release['prerelease'] and gamversion != 'prerelease':
|
||||
continue
|
||||
elif a_release['draft'] and gamversion != 'draft':
|
||||
continue
|
||||
release = a_release
|
||||
break
|
||||
for asset in release['assets']:
|
||||
if asset[sys.argv[1]].endswith('$gamfile'):
|
||||
print asset[sys.argv[1]]
|
||||
break"
|
||||
browser_download_url=$(echo "$release_json" | python -c "$pycode" browser_download_url $gamversion)
|
||||
name=$(echo "$release_json" | python -c "$pycode" name $gamversion)
|
||||
# Temp dir for archive
|
||||
temp_archive_dir=$(mktemp -d)
|
||||
|
||||
echo_yellow "Downloading file $name from $browser_download_url to $temp_archive_dir."
|
||||
# Save archive to temp w/o losing our path
|
||||
(cd $temp_archive_dir && curl -O -L $browser_download_url)
|
||||
|
||||
mkdir -p $target_dir
|
||||
|
||||
echo_yellow "Extracting archive to $target_dir"
|
||||
tar xf $temp_archive_dir/$name -C $target_dir
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
echo_red "ERROR: extracting the GAM archive with tar failed with error $rc. Exiting."
|
||||
exit
|
||||
else
|
||||
echo_green "Finished extracting GAM archive."
|
||||
fi
|
||||
|
||||
# Update profile to add gam command
|
||||
if [ "$update_profile" = true ]; then
|
||||
alias_line="alias gam=\"$target_dir/gam/gam\""
|
||||
if [ "$gamos" == "linux" ]; then
|
||||
update_profile "$HOME/.bashrc" || update_profile "$HOME/.bash_profile"
|
||||
elif [ "$gamos" == "macos" ]; then
|
||||
update_profile "$HOME/.profile" || update_profile "$HOME/.bash_profile"
|
||||
fi
|
||||
else
|
||||
echo_yellow "skipping profile update."
|
||||
fi
|
||||
|
||||
while true; do
|
||||
read -p "Can you run a full browser on this machine? (usually Y for MacOS, N for Linux if you SSH into this machine) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
break
|
||||
;;
|
||||
[Nn]*)
|
||||
touch $target_dir/gam/nobrowser.txt > /dev/null 2>&1
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo_red "Please answer yes or no."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
echo
|
||||
|
||||
project_created=false
|
||||
while true; do
|
||||
read -p "GAM is now installed. Are you ready to set up a Google API project for GAM? (yes or no) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
if [ "$adminuser" == "" ]; then
|
||||
read -p "Please enter your G Suite admin email address: " adminuser
|
||||
fi
|
||||
$target_dir/gam/gam create project $adminuser
|
||||
rc=$?
|
||||
if (( $rc == 0 )); then
|
||||
echo_green "Project creation complete."
|
||||
project_created=true
|
||||
break
|
||||
else
|
||||
echo_red "Projection creation failed. Trying again. Say N to skip projection creation."
|
||||
fi
|
||||
;;
|
||||
[Nn]*)
|
||||
echo -e "\nYou can create an API project later by running:\n\ngam create project\n"
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo_red "Please answer yes or no."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
admin_authorized=false
|
||||
while $project_created; do
|
||||
read -p "Are you ready to authorize GAM to perform G Suite management operations as your admin account? (yes or no) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
$target_dir/gam/gam oauth create $adminuser
|
||||
rc=$?
|
||||
if (( $rc == 0 )); then
|
||||
echo_green "Admin authorization complete."
|
||||
admin_authorized=true
|
||||
break
|
||||
else
|
||||
echo_red "Admin authorization failed. Trying again. Say N to skip admin authorization."
|
||||
fi
|
||||
;;
|
||||
[Nn]*)
|
||||
echo -e "\nYou can authorize an admin later by running:\n\ngam oauth create\n"
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo_red "Please answer yes or no."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
service_account_authorized=false
|
||||
while $project_created; do
|
||||
read -p "Are you ready to authorize GAM to manage G Suite user data and settings? (yes or no) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
if [ "$regularuser" == "" ]; then
|
||||
read -p "Please enter the email address of a regular G Suite user: " regularuser
|
||||
fi
|
||||
echo_yellow "Great! Checking service account scopes.This will fail the first time. Follow the steps to authorize and retry. It can take a few minutes for scopes to PASS after they've been authorized in the admin console."
|
||||
$target_dir/gam/gam user $adminuser check serviceaccount
|
||||
rc=$?
|
||||
if (( $rc == 0 )); then
|
||||
echo_green "Service account authorization complete."
|
||||
service_account_authorized=true
|
||||
break
|
||||
else
|
||||
echo_red "Service account authorization failed. Confirm you entered the scopes correctly in the admin console. It can take a few minutes for scopes to PASS after they are entered in the admin console so if you're sure you entered them correctly, go grab a coffee and then hit Y to try again. Say N to skip admin authorization."
|
||||
fi
|
||||
;;
|
||||
[Nn]*)
|
||||
echo -e "\nYou can authorize a service account later by running:\n\ngam check serviceaccount\n"
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo_red "Please answer yes or no."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo_green "Here's information about your new GAM installation:"
|
||||
$target_dir/gam/gam version
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
echo_red "ERROR: Failed running GAM for the first time with $rc. Please report this error to GAM mailing list. Exiting."
|
||||
exit
|
||||
fi
|
||||
|
||||
echo_green "GAM installation and setup complete!"
|
||||
if [ "$update_profile" = true ]; then
|
||||
echo_green "Please restart your terminal shell or to get started right away run:\n\n$alias_line"
|
||||
fi
|
||||
|
||||
# Clean up after ourselves even if we are killed with CTRL-C
|
||||
trap "rm -rf $temp_archive_dir" EXIT
|
||||
75
src/gam-setup.bat
Normal file
75
src/gam-setup.bat
Normal file
@@ -0,0 +1,75 @@
|
||||
@echo(
|
||||
@set /p adminemail= "Please enter your G Suite admin email address: "
|
||||
|
||||
:createproject
|
||||
@echo(
|
||||
@set /p yn= "Are you ready to set up a Google API project for GAM? [y or n] "
|
||||
@if /I "%yn%"=="n" (
|
||||
@ echo(
|
||||
@ echo You can create an API project later by running:
|
||||
@ echo(
|
||||
@ echo gam create project
|
||||
@ goto alldone
|
||||
)
|
||||
@if /I not "%yn%"=="y" (
|
||||
@ echo(
|
||||
@ echo Please answer y or n.
|
||||
@ goto createproject
|
||||
)
|
||||
@gam create project %adminemail%
|
||||
@if not ERRORLEVEL 1 goto projectdone
|
||||
@echo(
|
||||
@echo Projection creation failed. Trying again. Say n to skip projection creation.
|
||||
@goto createproject
|
||||
:projectdone
|
||||
|
||||
:adminauth
|
||||
@echo(
|
||||
@set /p yn= "Are you ready to authorize GAM to perform G Suite management operations as your admin account? [y or n] "
|
||||
@if /I "%yn%"=="n" (
|
||||
@ echo(
|
||||
@ echo You can authorize an admin later by running:
|
||||
@ echo(
|
||||
@ echo gam oauth create %adminemail%
|
||||
@ goto admindone
|
||||
)
|
||||
@if /I not "%yn%"=="y" (
|
||||
@ echo(
|
||||
@ echo Please answer y or n.
|
||||
@ goto adminauth
|
||||
)
|
||||
@gam oauth create %adminemail%
|
||||
@if not ERRORLEVEL 1 goto admindone
|
||||
@echo(
|
||||
@echo Admin authorization failed. Trying again. Say n to skip admin authorization.
|
||||
@goto adminauth
|
||||
:admindone
|
||||
|
||||
:saauth
|
||||
@echo(
|
||||
@set /p yn= "Are you ready to authorize GAM to manage G Suite user data and settings? [y or n] "
|
||||
@if /I "%yn%"=="n" (
|
||||
@ echo(
|
||||
@ echo You can authorize a service account later by running:
|
||||
@ echo(
|
||||
@ echo gam user %adminemail% check serviceaccount
|
||||
@ goto sadone
|
||||
)
|
||||
@if /I not "%yn%"=="y" (
|
||||
@ echo(
|
||||
@ echo Please answer y or n.
|
||||
@ goto saauth
|
||||
)
|
||||
@echo(
|
||||
@set /p regularuser= "Please enter the email address of a regular G Suite user: "
|
||||
@echo Great! Checking service account scopes. This will fail the first time. Follow the steps to authorize and retry. It can take a few minutes for scopes to PASS after they've been authorized in the admin console.
|
||||
@gam user %regularuser% check serviceaccount
|
||||
@if not ERRORLEVEL 1 goto sadone
|
||||
@echo(
|
||||
@echo Service account authorization failed. Confirm you entered the scopes correctly in the admin console. It can take a few minutes for scopes to PASS after they are entered in the admin console so if you're sure you entered them correctly, go grab a coffee and then hit Y to try again. Say N to skip admin authorization.
|
||||
@goto saauth
|
||||
:sadone
|
||||
|
||||
@echo GAM installation and setup complete!
|
||||
:alldone
|
||||
@pause
|
||||
304
src/gam.py
304
src/gam.py
@@ -23,7 +23,7 @@ For more information, see http://git.io/gam
|
||||
"""
|
||||
|
||||
__author__ = u'Jay Lee <jay0lee@gmail.com>'
|
||||
__version__ = u'3.8'
|
||||
__version__ = u'4.02'
|
||||
__license__ = u'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
import sys
|
||||
@@ -428,7 +428,7 @@ def indentMultiLineText(message, n=0):
|
||||
return message.replace(u'\n', u'\n{0}'.format(u' '*n)).rstrip()
|
||||
|
||||
def showUsage():
|
||||
doGAMVersion(checkForCheck=False)
|
||||
doGAMVersion(checkForArgs=False)
|
||||
print u'''
|
||||
Usage: gam [OPTIONS]...
|
||||
|
||||
@@ -818,25 +818,33 @@ def doGAMCheckForUpdates(forceCheck=False):
|
||||
except (urllib2.HTTPError, urllib2.URLError):
|
||||
return
|
||||
|
||||
def doGAMVersion(checkForCheck=True):
|
||||
import struct
|
||||
print u'GAM {0} - {1}\n{2}\nPython {3}.{4}.{5} {6}-bit {7}\ngoogle-api-python-client {8}\n{9} {10}\nPath: {11}'.format(__version__, GAM_URL,
|
||||
__author__,
|
||||
sys.version_info[0], sys.version_info[1], sys.version_info[2],
|
||||
struct.calcsize(u'P')*8, sys.version_info[3],
|
||||
googleapiclient.__version__,
|
||||
platform.platform(), platform.machine(),
|
||||
GM_Globals[GM_GAM_PATH])
|
||||
if checkForCheck:
|
||||
def doGAMVersion(checkForArgs=True):
|
||||
force_check = False
|
||||
simple = False
|
||||
if checkForArgs:
|
||||
i = 2
|
||||
while i < len(sys.argv):
|
||||
myarg = sys.argv[i].lower().replace(u'_', u'')
|
||||
if myarg == u'check':
|
||||
doGAMCheckForUpdates(forceCheck=True)
|
||||
force_check = True
|
||||
i += 1
|
||||
elif myarg == u'simple':
|
||||
simple = True
|
||||
i += 1
|
||||
else:
|
||||
print u'ERROR: %s is not a valid argument for "gam version"' % sys.argv[i]
|
||||
sys.exit(2)
|
||||
if simple:
|
||||
sys.stdout.write(__version__)
|
||||
return
|
||||
import struct
|
||||
version_data = u'GAM {0} - {1}\n{2}\nPython {3}.{4}.{5} {6}-bit {7}\ngoogle-api-python-client {8}\n{9} {10}\nPath: {11}'
|
||||
print version_data.format(__version__, GAM_URL, __author__, sys.version_info[0],
|
||||
sys.version_info[1], sys.version_info[2], struct.calcsize(u'P')*8,
|
||||
sys.version_info[3], googleapiclient.__version__, platform.platform(),
|
||||
platform.machine(), GM_Globals[GM_GAM_PATH])
|
||||
if force_check:
|
||||
doGAMCheckForUpdates(forceCheck=True)
|
||||
|
||||
def handleOAuthTokenError(e, soft_errors):
|
||||
if e.message in OAUTH2_TOKEN_ERRORS:
|
||||
@@ -960,7 +968,8 @@ def callGAPI(service, function,
|
||||
handleOAuthTokenError(e, soft_errors or GAPI_SERVICE_NOT_AVAILABLE in throw_reasons)
|
||||
if GAPI_SERVICE_NOT_AVAILABLE in throw_reasons:
|
||||
raise GAPI_serviceNotAvailable(e.message)
|
||||
entityUnknownWarning(u'User', GM_Globals[GM_CURRENT_API_USER], 0, 0)
|
||||
print u'ERROR: user %s: %s' % (GM_Globals[GM_CURRENT_API_USER], e)
|
||||
#entityUnknownWarning(u'User', GM_Globals[GM_CURRENT_API_USER], 0, 0)
|
||||
return None
|
||||
except httplib2.CertificateValidationUnsupported:
|
||||
noPythonSSLExit()
|
||||
@@ -1032,7 +1041,6 @@ API_VER_MAPPING = {
|
||||
u'datatransfer': u'datatransfer_v1',
|
||||
u'directory': u'directory_v1',
|
||||
u'drive': u'v2',
|
||||
u'email-audit': u'v1',
|
||||
u'email-settings': u'v2',
|
||||
u'gmail': u'v1',
|
||||
u'groupssettings': u'v1',
|
||||
@@ -1161,17 +1169,19 @@ def getSvcAcctAPIversionHttpService(api):
|
||||
except (ValueError, KeyError):
|
||||
invalidJSONExit(disc_file)
|
||||
|
||||
def buildGAPIServiceObject(api, act_as):
|
||||
def buildGAPIServiceObject(api, act_as, use_scopes=None):
|
||||
_, http, service = getSvcAcctAPIversionHttpService(api)
|
||||
GM_Globals[GM_CURRENT_API_USER] = act_as
|
||||
GM_Globals[GM_CURRENT_API_SCOPES] = API_SCOPE_MAPPING[api]
|
||||
credentials = getSvcAcctCredentials(GM_Globals[GM_CURRENT_API_SCOPES], act_as)
|
||||
if not use_scopes:
|
||||
use_scopes = GM_Globals[GM_CURRENT_API_SCOPES]
|
||||
credentials = getSvcAcctCredentials(use_scopes, act_as)
|
||||
try:
|
||||
service._http = credentials.authorize(http)
|
||||
except httplib2.ServerNotFoundError as e:
|
||||
systemErrorExit(4, e)
|
||||
except oauth2client.client.AccessTokenRefreshError as e:
|
||||
entityServiceNotApplicableWarning([u'Calendar', u'User'][api != u'calendar'], act_as, 0, 0)
|
||||
print u'ERROR user %s: %s' % (act_as, e)
|
||||
return handleOAuthTokenError(e, True)
|
||||
return service
|
||||
|
||||
@@ -1195,6 +1205,45 @@ def buildGplusGAPIObject(user):
|
||||
userEmail = convertUserUIDtoEmailAddress(user)
|
||||
return (userEmail, buildGAPIServiceObject(u'plus', userEmail))
|
||||
|
||||
def doCheckServiceAccount(users):
|
||||
for user in users:
|
||||
all_scopes_pass = True
|
||||
all_scopes = []
|
||||
print u'User: %s' % (user)
|
||||
for api, scopes in API_SCOPE_MAPPING.items():
|
||||
for scope in scopes:
|
||||
if scope in all_scopes:
|
||||
continue # don't check same scope twice
|
||||
all_scopes.append((api, scope))
|
||||
all_scopes = sorted(all_scopes)
|
||||
for scope in all_scopes:
|
||||
try:
|
||||
service = buildGAPIServiceObject(scope[0], act_as=user, use_scopes=scope[1])
|
||||
service._http.request.credentials.refresh(httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL]))
|
||||
result = u'PASS'
|
||||
except oauth2client.client.HttpAccessTokenRefreshError:
|
||||
result = u'FAIL'
|
||||
all_scopes_pass = False
|
||||
print u' Scope: {0:60} {1}'.format(scope[1], result)
|
||||
service_account = service._http.request.credentials.serialization_data[u'client_id']
|
||||
if all_scopes_pass:
|
||||
print u'\nAll scopes passed!\nService account %s is fully authorized.' % service_account
|
||||
else:
|
||||
user_domain = user[user.find(u'@')+1:]
|
||||
print u'''
|
||||
ERROR: Some scopes failed! Please go to:
|
||||
|
||||
https://admin.google.com/%s/AdminHome?#OGX:ManageOauthClients
|
||||
|
||||
and grant Client name:
|
||||
|
||||
%s
|
||||
|
||||
Access to scopes:
|
||||
|
||||
%s\n''' % (user_domain, service_account, ',\n'.join([scope[1] for scope in all_scopes]))
|
||||
sys.exit(int(not all_scopes_pass))
|
||||
|
||||
def showReport():
|
||||
|
||||
def _adjustDate(errMsg):
|
||||
@@ -3269,6 +3318,42 @@ def doCalendarWipeData():
|
||||
return
|
||||
callGAPI(cal.calendars(), u'clear', calendarId=calendarId)
|
||||
|
||||
def doCalendarDeleteEvent():
|
||||
calendarId, cal = buildCalendarGAPIObject(sys.argv[2])
|
||||
if not cal:
|
||||
return
|
||||
events = []
|
||||
sendNotifications = None
|
||||
doit = False
|
||||
i = 4
|
||||
while (i < len(sys.argv)):
|
||||
if sys.argv[i].lower() == u'notifyattendees':
|
||||
sendNotifications = True
|
||||
i += 1
|
||||
elif sys.argv[i].lower() in [u'id', u'eventid']:
|
||||
events.append(sys.argv[i+1])
|
||||
i += 2
|
||||
elif sys.argv[i].lower() in [u'query', u'eventquery']:
|
||||
query = sys.argv[i+1]
|
||||
result = callGAPIpages(cal.events(), u'list', items=u'items', calendarId=calendarId, q=query)
|
||||
for event in result:
|
||||
if u'id' in event:
|
||||
events.append(event[u'id'])
|
||||
i += 2
|
||||
elif sys.argv[i].lower() == u'doit':
|
||||
doit = True
|
||||
i += 1
|
||||
else:
|
||||
print u'ERROR: %s is not a valid argument for gam calendar <email> delete event'
|
||||
sys.exit(3)
|
||||
if doit:
|
||||
for eventId in events:
|
||||
print u' deleting eventId %s' % eventId
|
||||
callGAPI(cal.events(), u'delete', calendarId=calendarId, eventId=eventId)
|
||||
else:
|
||||
for eventId in events:
|
||||
print u' would delete eventId %s. Add doit to command to actually delete event' % eventId
|
||||
|
||||
def doCalendarAddEvent():
|
||||
calendarId, cal = buildCalendarGAPIObject(sys.argv[2])
|
||||
if not cal:
|
||||
@@ -6753,6 +6838,139 @@ def getUserAttributes(i, cd, updateCmd=False):
|
||||
body[u'hashFunction'] = u'crypt'
|
||||
return (body, admin_body)
|
||||
|
||||
def doCreateProject():
|
||||
try:
|
||||
login_hint = sys.argv[3]
|
||||
except IndexError:
|
||||
while True:
|
||||
login_hint = raw_input(u'\nWhat is your G Suite admin email address? ')
|
||||
if login_hint.find(u'@') == -1:
|
||||
print u'Error: that is not a valid email address'
|
||||
else:
|
||||
break
|
||||
from oauth2client.contrib.dictionary_storage import DictionaryStorage
|
||||
project_id = u'gam-project'
|
||||
for i in range(3):
|
||||
project_id += u'-%s' % ''.join(random.choice(string.digits + string.ascii_lowercase) for i in range(3))
|
||||
project_name = u'project:%s' % project_id
|
||||
scope = u'https://www.googleapis.com/auth/cloud-platform'
|
||||
client_id = u'297408095146-fug707qsjv4ikron0hugpevbrjhkmsk7.apps.googleusercontent.com'
|
||||
client_secret = u'qM3dP8f_4qedwzWQE1VR4zzU'
|
||||
flow = oauth2client.client.OAuth2WebServerFlow(client_id=client_id,
|
||||
client_secret=client_secret, scope=scope, redirect_uri=oauth2client.client.OOB_CALLBACK_URN,
|
||||
user_agent=GAM_INFO, access_type=u'online', response_type=u'code', login_hint=login_hint)
|
||||
flags = cmd_flags(noLocalWebserver=GC_Values[GC_NO_BROWSER])
|
||||
storage_dict = {}
|
||||
storage = DictionaryStorage(storage_dict, u'credentials')
|
||||
flags = cmd_flags(noLocalWebserver=GC_Values[GC_NO_BROWSER])
|
||||
http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL])
|
||||
try:
|
||||
credentials = oauth2client.tools.run_flow(flow=flow, storage=storage, flags=flags, http=http)
|
||||
except httplib2.CertificateValidationUnsupported:
|
||||
noPythonSSLExit()
|
||||
credentials.user_agent = GAM_INFO
|
||||
http = credentials.authorize(httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL],
|
||||
cache=GC_Values[GC_CACHE_DIR]))
|
||||
crm = googleapiclient.discovery.build(u'cloudresourcemanager', u'v1', http=http, cache_discovery=False)
|
||||
body = {u'projectId': project_id, u'name': u'GAM Project'}
|
||||
while True:
|
||||
create_again = False
|
||||
print u'Creating project "%s"...' % body[u'name']
|
||||
create_operation = callGAPI(crm.projects(), u'create', body=body)
|
||||
operation_name = create_operation[u'name']
|
||||
time.sleep(5) # Google recommends always waiting at least 5 seconds
|
||||
for i in range(1, 5):
|
||||
print u'Checking project status...'
|
||||
status = callGAPI(crm.operations(), u'get', name=operation_name)
|
||||
if u'error' in status:
|
||||
if u'message' in status[u'error'] and status[u'error'][u'message'].find(u'Callers must accept ToS') != -1:
|
||||
print u'''Please go to:
|
||||
|
||||
https://console.developers.google.com
|
||||
|
||||
and accept the Terms of Service (ToS). As soon as you've accepted the ToS popup, you can return here and press enter.'''
|
||||
raw_input()
|
||||
create_again = True
|
||||
break
|
||||
else:
|
||||
sys.exit(1)
|
||||
if u'done' in status and status[u'done']:
|
||||
break
|
||||
sleep_time = i ** 2
|
||||
print u'Project still being created. Sleeping %s seconds' % sleep_time
|
||||
time.sleep(sleep_time)
|
||||
if create_again:
|
||||
continue
|
||||
if not u'done' in status or not status[u'done']:
|
||||
print u'Failed to create project: %s' % status
|
||||
sys.exit(1)
|
||||
elif u'error' in status:
|
||||
print status[u'error']
|
||||
sys.exit(2)
|
||||
break
|
||||
|
||||
serveman = googleapiclient.discovery.build(u'servicemanagement', u'v1', http=http, cache_discovery=False)
|
||||
apis = [u'admin-json.googleapis.com', u'appsactivity-json.googleapis.com', u'calendar-json.googleapis.com',
|
||||
u'classroom.googleapis.com', u'drive', u'gmail-json.googleapis.com', u'groupssettings-json.googleapis.com',
|
||||
u'licensing-json.googleapis.com', u'plus-json.googleapis.com', u'contacts-json.googleapis.com']
|
||||
for api in apis:
|
||||
print u' enabling API %s...' % api
|
||||
enable_operation = callGAPI(serveman.services(), u'enable', serviceName=api, body={u'consumerId': project_name})
|
||||
iam = googleapiclient.discovery.build(u'iam', u'v1', http=http, cache_discovery=False)
|
||||
print u'Creating Service Account'
|
||||
service_account = callGAPI(iam.projects().serviceAccounts(), u'create', name=u'projects/%s' % project_id,
|
||||
body={u'accountId': project_id, u'serviceAccount': {u'displayName': u'GAM Project'}})
|
||||
body = {u'privateKeyType': u'TYPE_GOOGLE_CREDENTIALS_FILE', u'keyAlgorithm': u'KEY_ALG_RSA_4096'}
|
||||
key = callGAPI(iam.projects().serviceAccounts().keys(), u'create', name=service_account[u'name'], body=body)
|
||||
oauth2service_data = base64.b64decode(key[u'privateKeyData'])
|
||||
service_account_file = os.path.join(GM_Globals[GM_GAM_PATH], FN_OAUTH2SERVICE_JSON)
|
||||
if os.path.isfile(service_account_file):
|
||||
service_account_file = u'%s-%s' % (service_account_file, project_id)
|
||||
writeFile(service_account_file, oauth2service_data, continueOnError=False)
|
||||
console_credentials_url = u'https://console.developers.google.com/apis/credentials?project=%s' % project_id
|
||||
print u'''Please go to:
|
||||
|
||||
%s
|
||||
|
||||
1. Click the blue "Create credentials" button. Choose "OAuth client ID".
|
||||
2. Click the blue "Configure consent screen" button. Enter "GAM" for "Product name to show to users".
|
||||
3. Leave other fields blank. Click "Save" button.
|
||||
3. Choose "Other" and click the blue "Create" button.
|
||||
4. Copy your "client ID" value.
|
||||
|
||||
''' % console_credentials_url
|
||||
client_id = raw_input(u'Enter your Client ID: ')
|
||||
print u'\nNow go back to your browser and copy your client secret.'
|
||||
client_secret = raw_input(u'Enter your Client Secret: ')
|
||||
cs_data = u'''{
|
||||
"installed": {
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"client_id": "%s",
|
||||
"client_secret": "%s",
|
||||
"project_id": "%s",
|
||||
"redirect_uris": [
|
||||
"urn:ietf:wg:oauth:2.0:oob",
|
||||
"http://localhost"
|
||||
],
|
||||
"token_uri": "https://accounts.google.com/o/oauth2/token"
|
||||
}
|
||||
}''' % (client_id, client_secret, project_id)
|
||||
client_secrets_file = os.path.join(GM_Globals[GM_GAM_PATH], FN_CLIENT_SECRETS_JSON)
|
||||
if os.path.isfile(client_secrets_file):
|
||||
client_secrets_file = u'%s-%s' % (client_secrets_file, project_id)
|
||||
writeFile(client_secrets_file, cs_data, continueOnError=False)
|
||||
print u'''Almost there! Now please switch back to your browser and:
|
||||
|
||||
1. Click OK to close "OAuth client" popup if it's still open.
|
||||
2. Click "Manage service accounts" on the right of the screen.
|
||||
3. Click the 3 dots to the right of your service account.
|
||||
4. Choose Edit.
|
||||
5. Check the "Enable G Suite Domain-wide Delegation" box and click Save.
|
||||
'''
|
||||
raw_input(u'Press Enter when done...')
|
||||
print u'That\'s it! Your GAM Project is created and ready to use.'
|
||||
|
||||
def doCreateUser():
|
||||
cd = buildGAPIObject(u'directory')
|
||||
body, admin_body = getUserAttributes(3, cd, updateCmd=False)
|
||||
@@ -9760,7 +9978,7 @@ OAUTH2_MENU += '''
|
||||
OAUTH2_CMDS = [u's', u'u', u'e', u'c']
|
||||
MAXIMUM_SCOPES = 28
|
||||
|
||||
def doRequestOAuth():
|
||||
def doRequestOAuth(login_hint=None):
|
||||
def _checkMakeScopesList(scopes):
|
||||
del scopes[:]
|
||||
for i in range(num_scopes):
|
||||
@@ -9783,7 +10001,9 @@ def doRequestOAuth():
|
||||
MISSING_CLIENT_SECRETS_MESSAGE = u"""Please configure OAuth 2.0
|
||||
|
||||
To make GAM run you will need to populate the {0} file found at:
|
||||
|
||||
{1}
|
||||
|
||||
with information from the APIs Console <https://console.developers.google.com>.
|
||||
|
||||
See this site for instructions:
|
||||
@@ -9791,6 +10011,24 @@ See this site for instructions:
|
||||
|
||||
""".format(FN_CLIENT_SECRETS_JSON, GC_Values[GC_CLIENT_SECRETS_JSON], GAM_WIKI_CREATE_CLIENT_SECRETS)
|
||||
|
||||
cs_data = readFile(GC_Values[GC_CLIENT_SECRETS_JSON], mode=u'rb', continueOnError=True, displayError=True, encoding=None)
|
||||
if not cs_data:
|
||||
systemErrorExit(14, MISSING_CLIENT_SECRETS_MESSAGE)
|
||||
try:
|
||||
cs_json = json.loads(cs_data)
|
||||
client_id = cs_json[u'installed'][u'client_id']
|
||||
client_secret = cs_json[u'installed'][u'client_secret']
|
||||
except (ValueError, IndexError, KeyError):
|
||||
print u'ERROR: the format of your client secrets file:\n\n%s\n\n is incorrect. Please recreate the file.'
|
||||
sys.exit(3)
|
||||
|
||||
if not login_hint:
|
||||
while True:
|
||||
login_hint = raw_input(u'\nWhat is your G Suite admin email address? ')
|
||||
if login_hint.find(u'@') == -1:
|
||||
print u'Error: that is not a valid email address'
|
||||
else:
|
||||
break
|
||||
num_scopes = len(OAUTH2_SCOPES)
|
||||
menu = OAUTH2_MENU % tuple(range(num_scopes))
|
||||
selected_scopes = []
|
||||
@@ -9852,19 +10090,20 @@ See this site for instructions:
|
||||
status, message = _checkMakeScopesList(scopes)
|
||||
if status:
|
||||
break
|
||||
try:
|
||||
FLOW = oauth2client.client.flow_from_clientsecrets(GC_Values[GC_CLIENT_SECRETS_JSON], scope=scopes)
|
||||
except oauth2client.client.clientsecrets.InvalidClientSecretsError:
|
||||
systemErrorExit(14, MISSING_CLIENT_SECRETS_MESSAGE)
|
||||
flow = oauth2client.client.OAuth2WebServerFlow(client_id=client_id,
|
||||
client_secret=client_secret, scope=scopes, redirect_uri=oauth2client.client.OOB_CALLBACK_URN,
|
||||
user_agent=GAM_INFO, access_type=u'offline', response_type=u'code', login_hint=login_hint)
|
||||
storage = oauth2client.file.Storage(GC_Values[GC_OAUTH2_TXT])
|
||||
credentials = storage.get()
|
||||
flags = cmd_flags(noLocalWebserver=GC_Values[GC_NO_BROWSER])
|
||||
if credentials is None or credentials.invalid:
|
||||
http = httplib2.Http(disable_ssl_certificate_validation=GC_Values[GC_NO_VERIFY_SSL])
|
||||
try:
|
||||
credentials = oauth2client.tools.run_flow(flow=FLOW, storage=storage, flags=flags, http=http)
|
||||
credentials = oauth2client.tools.run_flow(flow=flow, storage=storage, flags=flags, http=http)
|
||||
except httplib2.CertificateValidationUnsupported:
|
||||
noPythonSSLExit()
|
||||
else:
|
||||
print u'It looks like you\'ve already authorized GAM. Refusing to overwrite existing file:\n\n%s' % GC_Values[GC_OAUTH2_TXT]
|
||||
|
||||
def batch_worker():
|
||||
while True:
|
||||
@@ -10043,6 +10282,8 @@ def ProcessGAMCommand(args):
|
||||
doCreateAdmin()
|
||||
elif argument in [u'guardianinvite', u'inviteguardian', u'guardian']:
|
||||
doInviteGuardian()
|
||||
elif argument in [u'project', u'apiproject']:
|
||||
doCreateProject()
|
||||
else:
|
||||
print u'ERROR: %s is not a valid argument for "gam create"' % argument
|
||||
sys.exit(2)
|
||||
@@ -10234,7 +10475,11 @@ def ProcessGAMCommand(args):
|
||||
elif command in [u'oauth', u'oauth2']:
|
||||
argument = sys.argv[2].lower()
|
||||
if argument in [u'request', u'create']:
|
||||
doRequestOAuth()
|
||||
try:
|
||||
login_hint = sys.argv[3]
|
||||
except IndexError:
|
||||
login_hint = None
|
||||
doRequestOAuth(login_hint)
|
||||
elif argument in [u'info', u'verify']:
|
||||
OAuthInfo()
|
||||
elif argument in [u'delete', u'revoke']:
|
||||
@@ -10257,6 +10502,8 @@ def ProcessGAMCommand(args):
|
||||
doCalendarWipeData()
|
||||
elif argument == u'addevent':
|
||||
doCalendarAddEvent()
|
||||
elif argument == u'deleteevent':
|
||||
doCalendarDeleteEvent()
|
||||
else:
|
||||
print u'ERROR: %s is not a valid argument for "gam calendar"' % argument
|
||||
sys.exit(2)
|
||||
@@ -10554,6 +10801,13 @@ def ProcessGAMCommand(args):
|
||||
else:
|
||||
print u'ERROR: %s is not a valid argument for "gam <users> info"' % infoWhat
|
||||
sys.exit(2)
|
||||
elif command == u'check':
|
||||
checkWhat = sys.argv[4].replace(u'_', '').lower()
|
||||
if checkWhat == u'serviceaccount':
|
||||
doCheckServiceAccount(users)
|
||||
else:
|
||||
print u'ERROR: %s is not a valid argument for "gam <users> check"' % checkWhat
|
||||
sys.exit(2)
|
||||
elif command == u'profile':
|
||||
doProfile(users)
|
||||
elif command == u'imap':
|
||||
|
||||
12
src/gam.wxs
12
src/gam.wxs
@@ -44,6 +44,7 @@
|
||||
Source="gam-64">
|
||||
<Component Id="gam_exe" Guid="886abc07-73c5-4acc-9f71-58daf62aabc1">
|
||||
<File Name="gam.exe" KeyPath="yes" />
|
||||
<Environment Id="PATH" Name="PATH" Value="[INSTALLFOLDER]" Permanent="yes" Part="last" Action="set" System="yes" />
|
||||
</Component>
|
||||
<Component Id="license" Guid="7a15de2e-fb91-4d0a-b8bf-c8b19c68f569">
|
||||
<File Name="LICENSE" KeyPath="yes" />
|
||||
@@ -51,6 +52,12 @@
|
||||
<Component Id="whatsnew_txt" Guid="6aa9863c-90d9-412f-9b73-fda82549a950">
|
||||
<File Name="whatsnew.txt" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="gam_setup_bat" Guid="ef01f93a-4b50-488a-9c04-ec5e13e66218">
|
||||
<File Name="gam-setup.bat" KeyPath="yes" />
|
||||
</Component>
|
||||
<Component Id="gamcommands_txt" Guid="58ff9c45-a7c9-4e22-8845-a9a92610c1f3">
|
||||
<File Name="gamcommands.txt" KeyPath="yes" />
|
||||
</Component>
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
|
||||
@@ -58,7 +65,10 @@
|
||||
<InstallUISequence>
|
||||
<ExecuteAction />
|
||||
<Show Dialog="WelcomeDlg" Before="ProgressDlg" />
|
||||
<!-- <Show Dialog="ProgressDlg" After="" /> -->
|
||||
</InstallUISequence>
|
||||
<CustomAction Id="setup_gam" ExeCommand="[INSTALLFOLDER]gam-setup.bat" Directory="INSTALLFOLDER" Execute="commit" Impersonate="yes" Return="asyncWait"/>
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="setup_gam" After="InstallFiles" >NOT Installed AND NOT UPGRADINGPRODUCTCODE AND NOT WIX_UPGRADE_DETECTED</Custom>
|
||||
</InstallExecuteSequence>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
|
||||
@@ -8,9 +8,8 @@ for d in a.datas:
|
||||
if 'pyconfig' in d[0]:
|
||||
a.datas.remove(d)
|
||||
break
|
||||
a.datas += [('httplib2/cacerts.txt', 'httplib2\cacerts.txt', 'DATA')]
|
||||
a.datas += [('httplib2/cacerts.txt', 'httplib2/cacerts.txt', 'DATA')]
|
||||
a.datas += [('cloudprint-v2.json', 'cloudprint-v2.json', 'DATA')]
|
||||
a.datas += [('email-audit-v1.json', 'email-audit-v1.json', 'DATA')]
|
||||
a.datas += [('email-settings-v2.json', 'email-settings-v2.json', 'DATA')]
|
||||
pyz = PYZ(a.pure)
|
||||
exe = EXE(pyz,
|
||||
@@ -21,5 +20,5 @@ exe = EXE(pyz,
|
||||
name='gam',
|
||||
debug=False,
|
||||
strip=None,
|
||||
upx=True,
|
||||
upx=False,
|
||||
console=True )
|
||||
|
||||
@@ -3,7 +3,7 @@ rmdir /q /s build
|
||||
rmdir /q /s dist
|
||||
rm -rf gam-$1-macos.tar.xz
|
||||
|
||||
pyinstaller --clean -F --distpath=gam macos-gam.spec
|
||||
/Library/Frameworks/Python.framework/Versions/2.7/bin/pyinstaller --clean -F --distpath=gam macos-gam.spec
|
||||
cp LICENSE gam
|
||||
cp whatsnew.txt gam
|
||||
|
||||
|
||||
@@ -8,9 +8,8 @@ for d in a.datas:
|
||||
if 'pyconfig' in d[0]:
|
||||
a.datas.remove(d)
|
||||
break
|
||||
a.datas += [('httplib2/cacerts.txt', 'httplib2\cacerts.txt', 'DATA')]
|
||||
a.datas += [('httplib2/cacerts.txt', 'httplib2/cacerts.txt', 'DATA')]
|
||||
a.datas += [('cloudprint-v2.json', 'cloudprint-v2.json', 'DATA')]
|
||||
a.datas += [('email-audit-v1.json', 'email-audit-v1.json', 'DATA')]
|
||||
a.datas += [('email-settings-v2.json', 'email-settings-v2.json', 'DATA')]
|
||||
pyz = PYZ(a.pure)
|
||||
exe = EXE(pyz,
|
||||
@@ -21,5 +20,5 @@ exe = EXE(pyz,
|
||||
name='gam',
|
||||
debug=False,
|
||||
strip=None,
|
||||
upx=True,
|
||||
upx=False,
|
||||
console=True )
|
||||
|
||||
@@ -273,7 +273,7 @@ class Credentials(object):
|
||||
to_serialize[key] = val.decode('utf-8')
|
||||
if isinstance(val, set):
|
||||
to_serialize[key] = list(val)
|
||||
return json.dumps(to_serialize)
|
||||
return json.dumps(to_serialize, indent=4, sort_keys=True)
|
||||
|
||||
def to_json(self):
|
||||
"""Creating a JSON representation of an instance of Credentials.
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
GAM 4.02
|
||||
- "gam create project" command simplifies creation of client_secrets.json and oauth2service.json project files.
|
||||
- Automated wizard simplifies GAM setup on Linux and MacOS (coming soon to Windows MSI).
|
||||
- "gam calendar <email> deleteevent" deletes events by ID or query
|
||||
|
||||
GAM 3.8
|
||||
- Old GData APIs removed from GAM. Admin Settings and Email Audit commands are no longer included, keep a copy of GAM 3.72 around if you use them. All Email Settings commands now use new Gmail API.
|
||||
- Updated httplib2, google-api-client, uritemplate libraries
|
||||
|
||||
@@ -11,12 +11,16 @@ del /q /f gam.wixpdb
|
||||
c:\python27-32\scripts\pyinstaller --clean -F --distpath=gam windows-gam.spec
|
||||
xcopy LICENSE gam\
|
||||
xcopy whatsnew.txt gam\
|
||||
xcopy gam-setup.bat gam\
|
||||
xcopy gamcommands.txt gam\
|
||||
del gam\w9xpopen.exe
|
||||
"%ProgramFiles%\7-Zip\7z.exe" a -tzip gam-%1-windows.zip gam\ -xr!.svn
|
||||
|
||||
c:\python27-64\scripts\pyinstaller --clean -F --distpath=gam-64 windows-gam.spec
|
||||
xcopy LICENSE gam-64\
|
||||
xcopy whatsnew.txt gam-64\
|
||||
xcopy gam-setup.bat gam-64\
|
||||
xcopy gamcommands.txt gam-64\
|
||||
"%ProgramFiles%\7-Zip\7z.exe" a -tzip gam-%1-windows-x64.zip gam-64\ -xr!.svn
|
||||
|
||||
set GAMVERSION=%1
|
||||
|
||||
@@ -11,7 +11,6 @@ for d in a.datas:
|
||||
break
|
||||
a.datas += [('httplib2/cacerts.txt', 'httplib2\cacerts.txt', 'DATA')]
|
||||
a.datas += [('cloudprint-v2.json', 'cloudprint-v2.json', 'DATA')]
|
||||
a.datas += [('email-audit-v1.json', 'email-audit-v1.json', 'DATA')]
|
||||
a.datas += [('email-settings-v2.json', 'email-settings-v2.json', 'DATA')]
|
||||
pyz = PYZ(a.pure)
|
||||
exe = EXE(pyz,
|
||||
|
||||
Reference in New Issue
Block a user