mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-04 14:21:39 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f233f7505c | ||
|
|
315367c80c | ||
|
|
aba602ff2a | ||
|
|
da8833bdc6 | ||
|
|
ed932cce55 | ||
|
|
37ade0059f | ||
|
|
7f6683e21d | ||
|
|
ca44a0dfb6 | ||
|
|
d9f85c600b | ||
|
|
c019a6d2e8 | ||
|
|
e8e25c352b | ||
|
|
1f75ac6112 | ||
|
|
0f111e6eaf | ||
|
|
8ee5b0ffdb | ||
|
|
8813f6c046 | ||
|
|
58fde587a8 | ||
|
|
06573991f6 | ||
|
|
597d3ac4b4 | ||
|
|
77a785049f | ||
|
|
44f62026a9 | ||
|
|
6e4bb0abf9 | ||
|
|
2b243edd3a | ||
|
|
b56fe39bb2 | ||
|
|
7447e73a37 | ||
|
|
c62fa88d00 | ||
|
|
fffb847b5f | ||
|
|
fe69068bba | ||
|
|
c7f9abde89 | ||
|
|
3dc755eafa | ||
|
|
3597594d3c | ||
|
|
f5a44c159b |
87
README.md
87
README.md
@@ -1,4 +1,85 @@
|
||||
gam
|
||||
===
|
||||
GAM, the Google Apps Manager
|
||||
============================
|
||||
|
||||
gam
|
||||
Dito GAM is a free, open source command line tool for
|
||||
Google Apps Administrators to efficiently manage
|
||||
domain and user settings quickly and easily. GAM has support
|
||||
for many features, such as
|
||||
|
||||
* creating, deleting, and updating users, aliases, groups,
|
||||
organizations, and resource calendars
|
||||
* modifying user email settings such as IMAP, signatures,
|
||||
vacation messages, profile sharing, email forwarding,
|
||||
send as address, labels, and features.
|
||||
* delegating mailboxes and calendars to other users
|
||||
* modifying calendar access rights for users and resource calendars.
|
||||
* auditing user accounts and mailboes
|
||||
* monitoring incoming and outgoing email
|
||||
* generating detailed reports for users, groups, resources,
|
||||
account activity, email clients, and quotas.
|
||||
|
||||
|
||||
Resources
|
||||
========
|
||||
|
||||
There are a number of GAM resources available via several different
|
||||
websites.
|
||||
|
||||
Source Repository
|
||||
-----------------
|
||||
|
||||
The official GAM source repository is on [Github][github].
|
||||
|
||||
|
||||
Downloads
|
||||
---------
|
||||
|
||||
You can download current and pre-released versions of GAM from
|
||||
the [Github Releases][github releases] page. Final releases
|
||||
are also available on [Google Drive][google drive]
|
||||
|
||||
|
||||
Wiki Documentation
|
||||
----
|
||||
|
||||
The GAM documentation is currently hosted on Google Code
|
||||
and can be found at the [Gam Getting Started Wiki][gam wiki]
|
||||
|
||||
Mailing List / Discussion group
|
||||
-------------------------------
|
||||
|
||||
The GAM mailing list / discussion group is hosted
|
||||
on [Google Groups]. You can join the list and interact
|
||||
via email, or just post from the web itself.
|
||||
|
||||
|
||||
Author
|
||||
======
|
||||
|
||||
GAM is maintained by <a href="mailto:jay0lee@gmail.com">Jay Lee</a>.
|
||||
|
||||
THANKS TO
|
||||
=========
|
||||
|
||||
GAM is made possible and maintained by the work of Dito.
|
||||
Who is Dito?
|
||||
|
||||
Dito is solely focused on moving organizations to Google's
|
||||
cloud. After hundreds of successful deployments over the
|
||||
last 5 years, we have gained notoriety for our complete
|
||||
understanding of the platform, our change management &
|
||||
training ability, and our rock-star deployment engineers.
|
||||
We are known worldwide as the Google Apps Experts.
|
||||
|
||||
Need a Google Apps Expert?
|
||||
[Contact Dito](http://ditoweb.com/contact), which offers
|
||||
[free premium GAM support](http://www.ditoweb.com/dito-gam)
|
||||
for domains that sign up through Dito.
|
||||
|
||||
|
||||
[github releases]: https://github.com/jay0lee/GAM/releases
|
||||
[github]: https://github.com/jay0lee/GAM/
|
||||
[google code downloads]: https://code.google.com/p/google-apps-manager/wiki/Downloads
|
||||
[google drive]: https://googledrive.com/host/0B0YvUuHHn3MnbFl6N0k1UXcwdVk/
|
||||
[gam wiki]: https://code.google.com/p/google-apps-manager/wiki/GAM3GettingStarted
|
||||
[google groups]: http://groups.google.com/group/google-apps-manager
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
rmdir /q /s gam
|
||||
rmdir /q /s gam-64
|
||||
rmdir /q /s python-src-%1
|
||||
rmdir /q /s build
|
||||
rmdir /q /s dist
|
||||
del /q /f gam-%1-python-src.zip
|
||||
del /q /f gam-%1-windows.zip
|
||||
del /q /f gam-%1-windows-x64.zip
|
||||
|
||||
|
||||
@@ -1269,7 +1269,7 @@ class OAuth2WebServerFlow(Flow):
|
||||
'client_secret': self.client_secret,
|
||||
'code': code,
|
||||
'redirect_uri': self.redirect_uri,
|
||||
'scope': self.scope,
|
||||
# 'scope': self.scope,
|
||||
})
|
||||
headers = {
|
||||
'content-type': 'application/x-www-form-urlencoded',
|
||||
|
||||
2
setup.py
2
setup.py
@@ -10,7 +10,7 @@ setup(
|
||||
options = {'py2exe':
|
||||
{'optimize': 2,
|
||||
'bundle_files': 1,
|
||||
'includes': ['passlib.handlers.sha2_crypt']'
|
||||
'includes': ['passlib.handlers.sha2_crypt'],
|
||||
'dist_dir' : 'gam'}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,147 +1,265 @@
|
||||
# Early, and incomplete implementation of -04.
|
||||
#
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
URI Template (RFC6570) Processor
|
||||
"""
|
||||
|
||||
__copyright__ = """\
|
||||
Copyright 2011-2013 Joe Gregorio
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
import re
|
||||
import urllib
|
||||
try:
|
||||
from urllib.parse import quote
|
||||
except ImportError:
|
||||
from urllib import quote
|
||||
|
||||
|
||||
|
||||
__version__ = "0.6"
|
||||
|
||||
RESERVED = ":/?#[]@!$&'()*+,;="
|
||||
OPERATOR = "+./;?|!@"
|
||||
EXPLODE = "*+"
|
||||
OPERATOR = "+#./;?&|!@"
|
||||
MODIFIER = ":^"
|
||||
TEMPLATE = re.compile(r"{(?P<operator>[\+\./;\?|!@])?(?P<varlist>[^}]+)}", re.UNICODE)
|
||||
VAR = re.compile(r"^(?P<varname>[^=\+\*:\^]+)((?P<explode>[\+\*])|(?P<partial>[:\^]-?[0-9]+))?(=(?P<default>.*))?$", re.UNICODE)
|
||||
|
||||
def _tostring(varname, value, explode, operator, safe=""):
|
||||
if type(value) == type([]):
|
||||
if explode == "+":
|
||||
return ",".join([varname + "." + urllib.quote(x, safe) for x in value])
|
||||
else:
|
||||
return ",".join([urllib.quote(x, safe) for x in value])
|
||||
if type(value) == type({}):
|
||||
keys = value.keys()
|
||||
keys.sort()
|
||||
if explode == "+":
|
||||
return ",".join([varname + "." + urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys])
|
||||
else:
|
||||
return ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys])
|
||||
else:
|
||||
return urllib.quote(value, safe)
|
||||
TEMPLATE = re.compile("{([^\}]+)}")
|
||||
|
||||
|
||||
def _tostring_path(varname, value, explode, operator, safe=""):
|
||||
joiner = operator
|
||||
if type(value) == type([]):
|
||||
if explode == "+":
|
||||
return joiner.join([varname + "." + urllib.quote(x, safe) for x in value])
|
||||
elif explode == "*":
|
||||
return joiner.join([urllib.quote(x, safe) for x in value])
|
||||
else:
|
||||
return ",".join([urllib.quote(x, safe) for x in value])
|
||||
elif type(value) == type({}):
|
||||
keys = value.keys()
|
||||
keys.sort()
|
||||
if explode == "+":
|
||||
return joiner.join([varname + "." + urllib.quote(key, safe) + joiner + urllib.quote(value[key], safe) for key in keys])
|
||||
elif explode == "*":
|
||||
return joiner.join([urllib.quote(key, safe) + joiner + urllib.quote(value[key], safe) for key in keys])
|
||||
else:
|
||||
return ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys])
|
||||
else:
|
||||
if value:
|
||||
return urllib.quote(value, safe)
|
||||
else:
|
||||
return ""
|
||||
def variables(template):
|
||||
'''Returns the set of keywords in a uri template'''
|
||||
vars = set()
|
||||
for varlist in TEMPLATE.findall(template):
|
||||
if varlist[0] in OPERATOR:
|
||||
varlist = varlist[1:]
|
||||
varspecs = varlist.split(',')
|
||||
for var in varspecs:
|
||||
# handle prefix values
|
||||
var = var.split(':')[0]
|
||||
# handle composite values
|
||||
if var.endswith('*'):
|
||||
var = var[:-1]
|
||||
vars.add(var)
|
||||
return vars
|
||||
|
||||
def _tostring_query(varname, value, explode, operator, safe=""):
|
||||
joiner = operator
|
||||
varprefix = ""
|
||||
if operator == "?":
|
||||
joiner = "&"
|
||||
varprefix = varname + "="
|
||||
if type(value) == type([]):
|
||||
if 0 == len(value):
|
||||
return ""
|
||||
if explode == "+":
|
||||
return joiner.join([varname + "=" + urllib.quote(x, safe) for x in value])
|
||||
elif explode == "*":
|
||||
return joiner.join([urllib.quote(x, safe) for x in value])
|
||||
|
||||
def _quote(value, safe, prefix=None):
|
||||
if prefix is not None:
|
||||
return quote(str(value)[:prefix], safe)
|
||||
return quote(str(value), safe)
|
||||
|
||||
|
||||
def _tostring(varname, value, explode, prefix, operator, safe=""):
|
||||
if isinstance(value, list):
|
||||
return ",".join([_quote(x, safe) for x in value])
|
||||
if isinstance(value, dict):
|
||||
keys = sorted(value.keys())
|
||||
if explode:
|
||||
return ",".join([_quote(key, safe) + "=" + \
|
||||
_quote(value[key], safe) for key in keys])
|
||||
else:
|
||||
return ",".join([_quote(key, safe) + "," + \
|
||||
_quote(value[key], safe) for key in keys])
|
||||
elif value is None:
|
||||
return
|
||||
else:
|
||||
return varprefix + ",".join([urllib.quote(x, safe) for x in value])
|
||||
elif type(value) == type({}):
|
||||
if 0 == len(value):
|
||||
return ""
|
||||
keys = value.keys()
|
||||
keys.sort()
|
||||
if explode == "+":
|
||||
return joiner.join([varname + "." + urllib.quote(key, safe) + "=" + urllib.quote(value[key], safe) for key in keys])
|
||||
elif explode == "*":
|
||||
return joiner.join([urllib.quote(key, safe) + "=" + urllib.quote(value[key], safe) for key in keys])
|
||||
return _quote(value, safe, prefix)
|
||||
|
||||
|
||||
def _tostring_path(varname, value, explode, prefix, operator, safe=""):
|
||||
joiner = operator
|
||||
if isinstance(value, list):
|
||||
if explode:
|
||||
out = [_quote(x, safe) for x in value if value is not None]
|
||||
else:
|
||||
joiner = ","
|
||||
out = [_quote(x, safe) for x in value if value is not None]
|
||||
if out:
|
||||
return joiner.join(out)
|
||||
else:
|
||||
return
|
||||
elif isinstance(value, dict):
|
||||
keys = sorted(value.keys())
|
||||
if explode:
|
||||
out = [_quote(key, safe) + "=" + \
|
||||
_quote(value[key], safe) for key in keys \
|
||||
if value[key] is not None]
|
||||
else:
|
||||
joiner = ","
|
||||
out = [_quote(key, safe) + "," + \
|
||||
_quote(value[key], safe) \
|
||||
for key in keys if value[key] is not None]
|
||||
if out:
|
||||
return joiner.join(out)
|
||||
else:
|
||||
return
|
||||
elif value is None:
|
||||
return
|
||||
else:
|
||||
return varprefix + ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys])
|
||||
else:
|
||||
if value:
|
||||
return varname + "=" + urllib.quote(value, safe)
|
||||
return _quote(value, safe, prefix)
|
||||
|
||||
|
||||
def _tostring_semi(varname, value, explode, prefix, operator, safe=""):
|
||||
joiner = operator
|
||||
if operator == "?":
|
||||
joiner = "&"
|
||||
if isinstance(value, list):
|
||||
if explode:
|
||||
out = [varname + "=" + _quote(x, safe) \
|
||||
for x in value if x is not None]
|
||||
if out:
|
||||
return joiner.join(out)
|
||||
else:
|
||||
return
|
||||
else:
|
||||
return varname + "=" + ",".join([_quote(x, safe) \
|
||||
for x in value])
|
||||
elif isinstance(value, dict):
|
||||
keys = sorted(value.keys())
|
||||
if explode:
|
||||
return joiner.join([_quote(key, safe) + "=" + \
|
||||
_quote(value[key], safe) \
|
||||
for key in keys if key is not None])
|
||||
else:
|
||||
return varname + "=" + ",".join([_quote(key, safe) + "," + \
|
||||
_quote(value[key], safe) for key in keys \
|
||||
if key is not None])
|
||||
else:
|
||||
return varname
|
||||
if value is None:
|
||||
return
|
||||
elif value:
|
||||
return (varname + "=" + _quote(value, safe, prefix))
|
||||
else:
|
||||
return varname
|
||||
|
||||
|
||||
def _tostring_query(varname, value, explode, prefix, operator, safe=""):
|
||||
joiner = operator
|
||||
if operator in ["?", "&"]:
|
||||
joiner = "&"
|
||||
if isinstance(value, list):
|
||||
if 0 == len(value):
|
||||
return None
|
||||
if explode:
|
||||
return joiner.join([varname + "=" + _quote(x, safe) \
|
||||
for x in value])
|
||||
else:
|
||||
return (varname + "=" + ",".join([_quote(x, safe) \
|
||||
for x in value]))
|
||||
elif isinstance(value, dict):
|
||||
if 0 == len(value):
|
||||
return None
|
||||
keys = sorted(value.keys())
|
||||
if explode:
|
||||
return joiner.join([_quote(key, safe) + "=" + \
|
||||
_quote(value[key], safe) \
|
||||
for key in keys])
|
||||
else:
|
||||
return varname + "=" + \
|
||||
",".join([_quote(key, safe) + "," + \
|
||||
_quote(value[key], safe) for key in keys])
|
||||
else:
|
||||
if value is None:
|
||||
return
|
||||
elif value:
|
||||
return (varname + "=" + _quote(value, safe, prefix))
|
||||
else:
|
||||
return (varname + "=")
|
||||
|
||||
|
||||
TOSTRING = {
|
||||
"" : _tostring,
|
||||
"+": _tostring,
|
||||
";": _tostring_query,
|
||||
"#": _tostring,
|
||||
";": _tostring_semi,
|
||||
"?": _tostring_query,
|
||||
"&": _tostring_query,
|
||||
"/": _tostring_path,
|
||||
".": _tostring_path,
|
||||
}
|
||||
|
||||
|
||||
def expand(template, vars):
|
||||
def _sub(match):
|
||||
groupdict = match.groupdict()
|
||||
operator = groupdict.get('operator')
|
||||
if operator is None:
|
||||
operator = ''
|
||||
varlist = groupdict.get('varlist')
|
||||
def expand(template, variables):
|
||||
"""
|
||||
Expand template as a URI Template using variables.
|
||||
"""
|
||||
def _sub(match):
|
||||
expression = match.group(1)
|
||||
operator = ""
|
||||
if expression[0] in OPERATOR:
|
||||
operator = expression[0]
|
||||
varlist = expression[1:]
|
||||
else:
|
||||
varlist = expression
|
||||
|
||||
safe = "@"
|
||||
if operator == '+':
|
||||
safe = RESERVED
|
||||
varspecs = varlist.split(",")
|
||||
varnames = []
|
||||
defaults = {}
|
||||
for varspec in varspecs:
|
||||
m = VAR.search(varspec)
|
||||
groupdict = m.groupdict()
|
||||
varname = groupdict.get('varname')
|
||||
explode = groupdict.get('explode')
|
||||
partial = groupdict.get('partial')
|
||||
default = groupdict.get('default')
|
||||
if default:
|
||||
defaults[varname] = default
|
||||
varnames.append((varname, explode, partial))
|
||||
safe = ""
|
||||
if operator in ["+", "#"]:
|
||||
safe = RESERVED
|
||||
varspecs = varlist.split(",")
|
||||
varnames = []
|
||||
defaults = {}
|
||||
for varspec in varspecs:
|
||||
default = None
|
||||
explode = False
|
||||
prefix = None
|
||||
if "=" in varspec:
|
||||
varname, default = tuple(varspec.split("=", 1))
|
||||
else:
|
||||
varname = varspec
|
||||
if varname[-1] == "*":
|
||||
explode = True
|
||||
varname = varname[:-1]
|
||||
elif ":" in varname:
|
||||
try:
|
||||
prefix = int(varname[varname.index(":")+1:])
|
||||
except ValueError:
|
||||
raise ValueError("non-integer prefix '{0}'".format(
|
||||
varname[varname.index(":")+1:]))
|
||||
varname = varname[:varname.index(":")]
|
||||
if default:
|
||||
defaults[varname] = default
|
||||
varnames.append((varname, explode, prefix))
|
||||
|
||||
retval = []
|
||||
joiner = operator
|
||||
prefix = operator
|
||||
if operator == "+":
|
||||
prefix = ""
|
||||
joiner = ","
|
||||
if operator == "?":
|
||||
joiner = "&"
|
||||
if operator == "":
|
||||
joiner = ","
|
||||
for varname, explode, partial in varnames:
|
||||
if varname in vars:
|
||||
value = vars[varname]
|
||||
#if not value and (type(value) == type({}) or type(value) == type([])) and varname in defaults:
|
||||
if not value and value != "" and varname in defaults:
|
||||
value = defaults[varname]
|
||||
elif varname in defaults:
|
||||
value = defaults[varname]
|
||||
else:
|
||||
continue
|
||||
retval.append(TOSTRING[operator](varname, value, explode, operator, safe=safe))
|
||||
if "".join(retval):
|
||||
return prefix + joiner.join(retval)
|
||||
else:
|
||||
return ""
|
||||
retval = []
|
||||
joiner = operator
|
||||
start = operator
|
||||
if operator == "+":
|
||||
start = ""
|
||||
joiner = ","
|
||||
if operator == "#":
|
||||
joiner = ","
|
||||
if operator == "?":
|
||||
joiner = "&"
|
||||
if operator == "&":
|
||||
start = "&"
|
||||
if operator == "":
|
||||
joiner = ","
|
||||
for varname, explode, prefix in varnames:
|
||||
if varname in variables:
|
||||
value = variables[varname]
|
||||
if not value and value != "" and varname in defaults:
|
||||
value = defaults[varname]
|
||||
elif varname in defaults:
|
||||
value = defaults[varname]
|
||||
else:
|
||||
continue
|
||||
expanded = TOSTRING[operator](
|
||||
varname, value, explode, prefix, operator, safe=safe)
|
||||
if expanded is not None:
|
||||
retval.append(expanded)
|
||||
if len(retval) > 0:
|
||||
return start + joiner.join(retval)
|
||||
else:
|
||||
return ""
|
||||
|
||||
return TEMPLATE.sub(_sub, template)
|
||||
return TEMPLATE.sub(_sub, template)
|
||||
|
||||
35
whatsnew.txt
35
whatsnew.txt
@@ -1,3 +1,36 @@
|
||||
GAM 3.4 "Oktoberfest"
|
||||
-Support for creating and setting custom user schemas http://goo.gl/M9rQrI
|
||||
-End user view of print users and user info commands.
|
||||
-fix updating name/description for groups
|
||||
-fix groups sync commands
|
||||
-gam print groups members no longer fails on zero member groups
|
||||
-make sure downloaded Drive file names are safe for OS filenames.
|
||||
-gam info domain should no longer crash on getting customer ID
|
||||
-other minor bug fixes
|
||||
|
||||
GAM 3.32
|
||||
-fix service account json files downloaded from new cloud console don't work with GAM
|
||||
-use the new Gmail API for label command. Offers ability to rename labels and show/hide labels from the label and message list.
|
||||
-copy Google drive files with commands like gam user <email> update drivefile id <fileid> copy
|
||||
-print only one SKU for licenses with commands like gam print licenses sku vault
|
||||
-short license names for Google Apps Message Security (gams), Google Apps Unlimtied (gau) and Google Vault Former Employee (vfe)
|
||||
|
||||
GAM 3.31
|
||||
-New command "gam user delete aliases" clears all aliases for user.
|
||||
-"gam update user email vfe" will rename user to vfe.oldname.XXXXX@olddomain.com
|
||||
-fix delete photo command
|
||||
-fix password update/create for Windows builds
|
||||
|
||||
GAM 3.3
|
||||
-Major rewrites of "gam report" and "gam print users". Note that CSV headers have changed. Better performance and print users now supports "rich" fields like organization, phone, relations, etc.
|
||||
-"gam report drive" now works for Google Apps Unlimited domains.
|
||||
-"gam info user" now shows the user's licenses. Use "nolicenses" to prevent showing licenses.
|
||||
-fix "gam print licenses" fails in large domains by reducing page size from 1000 to 100
|
||||
-GAM now sends a sha-512 salted hashed password on user create and update for better security.
|
||||
-cleanup unused features of old GData library.
|
||||
-fix for Drive file downloading default format.
|
||||
-upgrade to httplib v0.9 which may help with httplib.BadStatus errors.
|
||||
|
||||
GAM 3.21
|
||||
-Fix crash when attempting to perform Drive operations for users with Drive service disabled.
|
||||
-Fix "gam info org" only prints first 100 users in org.
|
||||
@@ -222,4 +255,4 @@ GAM 1.8
|
||||
|
||||
-The settings filter command http://code.google.com/p/google-apps-manager/wiki/ExamplesEmailSettings#Create_a_Filter now has more actions including forward, star, trash and never send to spam.
|
||||
|
||||
-Downloading Audit Exports now has partial resume support. GAM will not re-download files that already exist on the local drive. If a large export download fails you should delete the last file GAM was working on since it's incomplete and then restart the process, GAM will pick up with the last file.
|
||||
-Downloading Audit Exports now has partial resume support. GAM will not re-download files that already exist on the local drive. If a large export download fails you should delete the last file GAM was working on since it's incomplete and then restart the process, GAM will pick up with the last file.
|
||||
|
||||
Reference in New Issue
Block a user