Compare commits

..

21 Commits
v3.32 ... v3.4

Author SHA1 Message Date
Jay Lee
f233f7505c update whatsnew.txt 2014-09-24 07:55:58 -04:00
Jay Lee
315367c80c fix cd.schemas() calls 2014-09-23 21:22:01 -04:00
Jay Lee
aba602ff2a GAM 3.4 "Oktoberfest" 2014-09-23 21:12:11 -04:00
Jay Lee
da8833bdc6 make "gam create ou" work 2014-09-23 21:10:45 -04:00
Jay Lee
ed932cce55 sort domain settings customerId results to make sure a real user is returned. 2014-09-23 21:09:55 -04:00
Jay Lee
37ade0059f handle missing dns.resolver library quietly 2014-09-23 21:08:58 -04:00
Jay Lee
7f6683e21d language shouldn't lowercase 2014-09-23 21:07:54 -04:00
Jay Lee
ca44a0dfb6 make sure downloaded drive file names are safe 2014-09-23 21:07:18 -04:00
Jay Lee
d9f85c600b handle IndexError on page messages 2014-09-23 21:06:30 -04:00
Jay Lee
c019a6d2e8 Merge branch 'master' of github.com:jay0lee/GAM
yes
2014-09-23 21:04:46 -04:00
Jay Lee
e8e25c352b add user schema api scopes 2014-09-23 20:09:05 -04:00
Jay Lee
1f75ac6112 Support for custom user schemas and non-admin user information 2014-09-23 19:53:08 -04:00
Jay Lee
0f111e6eaf update uritemplate 2014-09-12 07:20:11 -04:00
Jay Lee
8ee5b0ffdb Merge pull request #3 from daethnir/client_secrets_url
Adds link to docs for creating client secrets file.
2014-09-12 05:15:53 -04:00
Jay Lee
8813f6c046 Merge branch 'master' of github.com:jay0lee/GAM 2014-09-12 05:14:08 -04:00
Jay Lee
58fde587a8 git push origin masterMerge branch 'ksdtech-master' 2014-09-12 05:13:20 -04:00
Jay Lee
06573991f6 Merge branch 'master' of https://github.com/ksdtech/GAM into ksdtech-master 2014-09-12 05:09:30 -04:00
Jay Lee
597d3ac4b4 don't try to delete python source file directory in build.bat 2014-09-11 15:28:11 -04:00
Peter Zingg
c62fa88d00 'members' items key for members().list() GAPI call
The default 'items' key is not found when calling the members().list() GAPI, so no existing group members are returned from callGAPIpages. The correct key is 'members'.
2014-07-28 09:50:43 -07:00
Peter Zingg
fffb847b5f allow name and description settings in doUpdateGroup
You can set 'name' and 'description' separately in doCreateGroup, but the corresponding code in doUpdateGroup is not available. Using the settings interface for these parameters seems to work.  Also, the prompt for illegal attributes was incorrect. Should say 'gam update group...', not 'gam create group...'
2014-07-27 14:42:00 -07:00
Bri Hatch
fe69068bba Adds link to docs for creating client secrets file. 2014-07-15 14:03:52 -07:00
5 changed files with 545 additions and 183 deletions

View File

@@ -1,9 +1,7 @@
rmdir /q /s gam rmdir /q /s gam
rmdir /q /s gam-64 rmdir /q /s gam-64
rmdir /q /s python-src-%1
rmdir /q /s build rmdir /q /s build
rmdir /q /s dist 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.zip
del /q /f gam-%1-windows-x64.zip del /q /f gam-%1-windows-x64.zip

323
gam.py
View File

@@ -24,7 +24,7 @@ For more information, see http://code.google.com/p/google-apps-manager
""" """
__author__ = u'Jay Lee <jay0lee@gmail.com>' __author__ = u'Jay Lee <jay0lee@gmail.com>'
__version__ = u'3.32' __version__ = u'3.4'
__license__ = u'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' __license__ = u'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
import sys, os, time, datetime, random, socket, csv, platform, re, calendar, base64, hashlib import sys, os, time, datetime, random, socket, csv, platform, re, calendar, base64, hashlib
@@ -447,17 +447,17 @@ def callGAPIpages(service, function, items=u'items', nextPageToken=u'nextPageTok
show_message = page_message show_message = page_message
try: try:
show_message = show_message.replace(u'%%num_items%%', str(page_items)) show_message = show_message.replace(u'%%num_items%%', str(page_items))
except KeyError: except (IndexError, KeyError):
show_message = show_message.replace(u'%%num_items%%', '0') show_message = show_message.replace(u'%%num_items%%', '0')
try: try:
show_message = show_message.replace(u'%%total_items%%', str(total_items)) show_message = show_message.replace(u'%%total_items%%', str(total_items))
except KeyError: except (IndexError, KeyError):
show_message = show_message.replace(u'%%total_items%%', '0') show_message = show_message.replace(u'%%total_items%%', '0')
if message_attribute: if message_attribute:
try: try:
show_message = show_message.replace(u'%%first_item%%', str(this_page[items][0][message_attribute])) show_message = show_message.replace(u'%%first_item%%', str(this_page[items][0][message_attribute]))
show_message = show_message.replace(u'%%last_item%%', str(this_page[items][-1][message_attribute])) show_message = show_message.replace(u'%%last_item%%', str(this_page[items][-1][message_attribute]))
except KeyError: except (IndexError, KeyError):
show_message = show_message.replace(u'%%first_item%%', '') show_message = show_message.replace(u'%%first_item%%', '')
show_message = show_message.replace(u'%%last_item%%', '') show_message = show_message.replace(u'%%last_item%%', '')
restart_line() restart_line()
@@ -467,7 +467,7 @@ def callGAPIpages(service, function, items=u'items', nextPageToken=u'nextPageTok
pageToken = this_page[nextPageToken] pageToken = this_page[nextPageToken]
if pageToken == '': if pageToken == '':
return all_pages return all_pages
except KeyError: except (IndexError, KeyError):
return all_pages return all_pages
def getAPIVer(api): def getAPIVer(api):
@@ -2214,6 +2214,7 @@ def downloadDriveFile(users):
query = fileIds = None query = fileIds = None
gdownload_format = u'openoffice' gdownload_format = u'openoffice'
target_folder = getGamPath() target_folder = getGamPath()
safe_filename_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
while i < len(sys.argv): while i < len(sys.argv):
if sys.argv[i].lower() == u'id': if sys.argv[i].lower() == u'id':
fileIds = [sys.argv[i+1],] fileIds = [sys.argv[i+1],]
@@ -2263,7 +2264,7 @@ def downloadDriveFile(users):
if fileIds[0].find('/') != -1: if fileIds[0].find('/') != -1:
fileIds[0] = fileIds[0][:fileIds[0].find('/')] fileIds[0] = fileIds[0][:fileIds[0].find('/')]
if not fileIds: if not fileIds:
print u'No files to delete for %s' % user print u'No files to download for %s' % user
i = 0 i = 0
for fileId in fileIds: for fileId in fileIds:
extension = None extension = None
@@ -2295,7 +2296,9 @@ def downloadDriveFile(users):
except KeyError: except KeyError:
pass pass
break break
filename = u'%s/%s' % (target_folder, result[u'title']) file_title = result[u'title']
safe_file_title = ''.join(c for c in file_title if c in safe_filename_chars)
filename = u'%s/%s' % (target_folder, safe_file_title)
if extension and filename.lower()[:len(extension)] != extension: if extension and filename.lower()[:len(extension)] != extension:
filename = u'%s%s' % (filename, extension) filename = u'%s%s' % (filename, extension)
y = 0 y = 0
@@ -2664,7 +2667,7 @@ def showSendAs(users):
print u'' print u''
def doLanguage(users): def doLanguage(users):
language = sys.argv[4].lower() language = sys.argv[4]
emailsettings = getEmailSettingsObject() emailsettings = getEmailSettingsObject()
count = len(users) count = len(users)
i = 1 i = 1
@@ -3270,6 +3273,104 @@ def getVacation(users):
except TypeError: except TypeError:
pass pass
def doCreateDomain():
cd = buildGAPIObject(u'directory')
domain_name = sys.argv[3]
domain_type = sys.argv[4].upper()
if domain_type.replace(u'_', '') in [u'ALIAS', u'MIRROR', u'DOMAINALIAS', u'ALIAS_DOMAIN']:
domain_type = u'DOMAIN_ALIAS'
elif domain_type.replace(u'_', '') in [u'SECONDARY', u'SEPARATE', u'MULTIDOMAIN', u'MULTI']:
domain_type = u'MULTI_DOMAIN'
elif domain_type.replace(u'_', '') in [u'PRIMARY', u'PRIMARYDOMAIN', u'HOME']:
domain_type = u'PRIMARY'
elif domain_type != u'UNKNOWN':
print u'Error: domain type should be alias, secondary, primary or unknown. Got %s' % domain_type
sys.exit(4)
body = {u'domain_name': domain_name, u'domain_type': domain_type}
callGAPI(service=cd.domains(), function=u'insert', customerId=customerId, body=body)
print u'Added domain %s' % domain_name
def doDelSchema():
cd = buildGAPIObject(u'directory')
schemaKey = sys.argv[3]
callGAPI(service=cd.schemas(), function=u'delete', customerId=customerId, schemaKey=schemaKey)
print u'Deleted schema %s' % schemaKey
def doCreateOrUpdateUserSchema():
cd = buildGAPIObject(u'directory')
schemaName = sys.argv[3]
body = {u'schemaName': schemaName, u'fields': []}
i = 4
while i < len(sys.argv):
if sys.argv[i] in [u'field']:
a_field = {u'fieldName': sys.argv[i+1]}
i += 2
while True:
if sys.argv[i].lower() in [u'type']:
a_field[u'fieldType'] = sys.argv[i+1].upper()
if a_field[u'fieldType'] not in [u'BOOL', u'DOUBLE', u'EMAIL', u'INT64', u'PHONE', u'STRING']:
print 'Error: type must be bool, double, email, int64, phone or string. Got %s' % a_field[u'fieldType']
sys.exit(3)
i += 2
elif sys.argv[i].lower() in [u'multivalued']:
a_field[u'multiValued'] = True
i += 1
elif sys.argv[i].lower() in [u'indexed']:
a_field[u'indexed'] = True
i += 1
elif sys.argv[i].lower() in [u'restricted']:
a_field[u'readAccessType'] = u'ADMINS_AND_SELF'
i += 1
elif sys.argv[i].lower() in [u'range']:
a_field[u'numericIndexingSpec'] = {u'minValue': sys.argv[i+1], u'maxValue': sys.argv[i+2]}
i += 3
elif sys.argv[i].lower() in [u'endfield']:
body[u'fields'].append(a_field)
i += 1
break
else:
print 'Error: %s is not a valid argument to gam create schema' % sys.argv[i]
sys.exit(4)
if sys.argv[1].lower() == u'create':
result = callGAPI(service=cd.schemas(), function=u'insert', customerId=customerId, body=body)
print 'Created user schema %s' % result[u'schemaName']
elif sys.argv[1].lower() == u'update':
result = callGAPI(service=cd.schemas(), function=u'update', customerId=customerId, body=body, schemaKey=schemaName)
print 'Updated user schema %s' % result[u'schemaName']
def doPrintUserSchemas():
cd = buildGAPIObject(u'directory')
schemas = callGAPI(service=cd.schemas(), function=u'list', customerId=customerId)
for schema in schemas[u'schemas']:
print u'Schema: %s' % schema[u'schemaName']
for a_key in schema.keys():
if a_key not in [u'schemaName', u'fields', u'etag', u'kind']:
print '%s: %s' % (a_key, schema[a_key])
print
for field in schema[u'fields']:
print u' Field: %s' % field[u'fieldName']
for a_key in field.keys():
if a_key not in [u'fieldName', u'kind', u'etag']:
print ' %s: %s' % (a_key, field[a_key])
print
print
def doGetUserSchema():
cd = buildGAPIObject(u'directory')
schemaKey = sys.argv[3]
schema = callGAPI(service=cd.schemas(), function=u'get', customerId=customerId, schemaKey=schemaKey)
print u'Schema: %s' % schema[u'schemaName']
for a_key in schema.keys():
if a_key not in [u'schemaName', u'fields', u'etag', u'kind']:
print '%s: %s' % (a_key, schema[a_key])
print
for field in schema[u'fields']:
print u' Field: %s' % field[u'fieldName']
for a_key in field.keys():
if a_key not in [u'fieldName', u'kind', u'etag']:
print ' %s: %s' % (a_key, field[a_key])
print
def doCreateUser(): def doCreateUser():
cd = buildGAPIObject(u'directory') cd = buildGAPIObject(u'directory')
body = dict() body = dict()
@@ -3567,9 +3668,33 @@ def doCreateUser():
except KeyError: except KeyError:
body[u'externalIds'] = [externalid,] body[u'externalIds'] = [externalid,]
i += 1 i += 1
# else:
# showUsage()
# sys.exit(2)
else: else:
showUsage() if u'customSchemas' not in body:
sys.exit(2) body[u'customSchemas'] = {}
try:
(schemaName, fieldName) = sys.argv[i].split(u'.')
except ValueError:
print 'Error: %s is not a valid create user argument or custom schema name.' % sys.argv[i]
sys.exit(3)
field_value = sys.argv[i+1]
is_multivalue = False
if field_value.lower() in [u'multivalue', u'multivalued', u'value']:
is_multivalue = True
field_value = sys.argv[i+2]
if schemaName not in body[u'customSchemas']:
body[u'customSchemas'][schemaName] = {}
if is_multivalue:
if fieldName not in body[u'customSchemas'][schemaName]:
body[u'customSchemas'][schemaName][fieldName] = []
body[u'customSchemas'][schemaName][fieldName].append({u'value': field_value})
else:
body[u'customSchemas'][schemaName][fieldName] = field_value
i += 2
if is_multivalue:
i += 1
if not gotFirstName: if not gotFirstName:
body[u'name'][u'givenName'] = u'Unknown' body[u'name'][u'givenName'] = u'Unknown'
if not gotLastName: if not gotLastName:
@@ -4039,11 +4164,36 @@ def doUpdateUser(users):
except KeyError: except KeyError:
body[u'externalIds'] = [externalid,] body[u'externalIds'] = [externalid,]
i += 1 i += 1
# else:
# showUsage()
# print u''
# print u'Error: didn\'t expect %s command at position %s' % (sys.argv[i], i)
# sys.exit(2)
else: else:
showUsage() do_update_user = True
print u'' if u'customSchemas' not in body:
print u'Error: didn\'t expect %s command at position %s' % (sys.argv[i], i) body[u'customSchemas'] = {}
sys.exit(2) try:
(schemaName, fieldName) = sys.argv[i].split(u'.')
except ValueError:
print u'Error: %s is not a valid user update argument or custom schema name' % sys.argv[i]
sys.exit(3)
field_value = sys.argv[i+1]
is_multivalue = False
if field_value.lower() == u'multivalue':
is_multivalue = True
field_value = sys.argv[i+2]
if schemaName not in body[u'customSchemas']:
body[u'customSchemas'][schemaName] = {}
if is_multivalue:
if fieldName not in body[u'customSchemas'][schemaName]:
body[u'customSchemas'][schemaName][fieldName] = []
body[u'customSchemas'][schemaName][fieldName].append({u'value': field_value})
else:
body[u'customSchemas'][schemaName][fieldName] = field_value
i += 2
if is_multivalue:
i += 1
if gotPassword and not (isSHA1 or isMD5 or isCrypt or nohash): if gotPassword and not (isSHA1 or isMD5 or isCrypt or nohash):
body[u'password'] = gen_sha512_hash(body[u'password']) body[u'password'] = gen_sha512_hash(body[u'password'])
body[u'hashFunction'] = u'crypt' body[u'hashFunction'] = u'crypt'
@@ -4185,7 +4335,7 @@ def doUpdateGroup():
gs_object = buildDiscoveryObject(u'groupssettings') gs_object = buildDiscoveryObject(u'groupssettings')
matches_gs_setting = False matches_gs_setting = False
for (attrib, params) in gs_object[u'schemas'][u'Groups'][u'properties'].items(): for (attrib, params) in gs_object[u'schemas'][u'Groups'][u'properties'].items():
if attrib in [u'kind', u'etag', u'email', u'name', u'description']: if attrib in [u'kind', u'etag', u'email']:
continue continue
if sys.argv[i].lower().replace(u'_', u'') == attrib.lower(): if sys.argv[i].lower().replace(u'_', u'') == attrib.lower():
matches_gs_setting = True matches_gs_setting = True
@@ -4211,7 +4361,7 @@ def doUpdateGroup():
value = u'false' value = u'false'
break break
if not matches_gs_setting: if not matches_gs_setting:
print u'ERROR: %s is not a valid argument for "gam create group..."' % sys.argv[i] print u'ERROR: %s is not a valid argument for "gam update group..."' % sys.argv[i]
sys.exit(9) sys.exit(9)
gs_body[attrib] = value gs_body[attrib] = value
use_gs_api = True use_gs_api = True
@@ -4472,7 +4622,9 @@ def doGetUserInfo(user_email=None):
user_email = user_email[4:] user_email = user_email[4:]
elif user_email.find(u'@') == -1: elif user_email.find(u'@') == -1:
user_email = u'%s@%s' % (user_email, domain) user_email = u'%s@%s' % (user_email, domain)
getAliases = getGroups = getLicenses = True getSchemas = getAliases = getGroups = getLicenses = True
projection = u'full'
customFieldMask = viewType = None
i = 4 i = 4
while i < len(sys.argv): while i < len(sys.argv):
if sys.argv[i].lower() == u'noaliases': if sys.argv[i].lower() == u'noaliases':
@@ -4484,10 +4636,23 @@ def doGetUserInfo(user_email=None):
elif sys.argv[i].lower() == u'nolicenses': elif sys.argv[i].lower() == u'nolicenses':
getLicenses = False getLicenses = False
i += 1 i += 1
elif sys.argv[i].lower() == u'noschemas':
getSchemas = False
projection = u'basic'
i += 1
elif sys.argv[i].lower() == u'schemas':
getSchemas = True
projection = u'custom'
customFieldMask = sys.argv[i+1]
i += 2
elif sys.argv[i].lower() == u'userview':
viewType = u'domain_public'
getGroups = getLicenses = False
i += 1
else: else:
print u'%s is not a valid argument for gam info user' % sys.argv[i] print u'%s is not a valid argument for gam info user' % sys.argv[i]
sys.exit(3) sys.exit(3)
user = callGAPI(service=cd.users(), function=u'get', userKey=user_email) user = callGAPI(service=cd.users(), function=u'get', userKey=user_email, projection=projection, customFieldMask=customFieldMask, viewType=viewType)
print u'User: %s' % user[u'primaryEmail'] print u'User: %s' % user[u'primaryEmail']
if u'name' in user and u'givenName' in user[u'name']: if u'name' in user and u'givenName' in user[u'name']:
print u'First Name: %s' % user[u'name'][u'givenName'] print u'First Name: %s' % user[u'name'][u'givenName']
@@ -4586,6 +4751,19 @@ def doGetUserInfo(user_email=None):
else: else:
print u' %s: %s' % (key, id[key]) print u' %s: %s' % (key, id[key])
print u'' print u''
if getSchemas:
print u'Custom Schemas:'
if u'customSchemas' in user:
for schema in user[u'customSchemas']:
print u' Schema: %s' % schema
for field in user[u'customSchemas'][schema]:
if type(user[u'customSchemas'][schema][field]) is list:
print ' %s:' % field
for an_item in user[u'customSchemas'][schema][field]:
print ' %s' % an_item[u'value']
else:
print u' %s: %s' % (field, user[u'customSchemas'][schema][field])
print
if getAliases: if getAliases:
if u'aliases' in user: if u'aliases' in user:
print u'Email Aliases:' print u'Email Aliases:'
@@ -4860,28 +5038,34 @@ def doSiteVerifyAttempt():
print u'Method: %s' % verify_data[u'method'] print u'Method: %s' % verify_data[u'method']
print u'Token: %s' % verify_data[u'token'] print u'Token: %s' % verify_data[u'token']
if verify_data[u'method'] == u'DNS_CNAME': if verify_data[u'method'] == u'DNS_CNAME':
import dns.resolver
resolver = dns.resolver.Resolver()
resolver.nameservers = nameservers=[u'8.8.8.8', u'8.8.4.4']
cname_token = verify_data[u'token']
cname_list = cname_token.split(u' ')
cname_subdomain = cname_list[0]
try: try:
answers = resolver.query(u'%s.%s' % (cname_subdomain, a_domain), u'A') import dns.resolver
for answer in answers: resolver = dns.resolver.Resolver()
print u'DNS Record: %s' % answer resolver.nameservers = nameservers=[u'8.8.8.8', u'8.8.4.4']
except dns.resolver.NXDOMAIN: cname_token = verify_data[u'token']
print u'ERROR: No such domain found in DNS!' cname_list = cname_token.split(u' ')
cname_subdomain = cname_list[0]
try:
answers = resolver.query(u'%s.%s' % (cname_subdomain, a_domain), u'A')
for answer in answers:
print u'DNS Record: %s' % answer
except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer):
print u'ERROR: No such domain found in DNS!'
except ImportError:
pass
elif verify_data[u'method'] == u'DNS_TXT': elif verify_data[u'method'] == u'DNS_TXT':
import dns.resolver
resolver = dns.resolver.Resolver()
resolver.nameservers = nameservers=[u'8.8.8.8', u'8.8.4.4']
try: try:
answers = resolver.query(a_domain, u'TXT') import dns.resolver
for answer in answers: resolver = dns.resolver.Resolver()
print u'DNS Record: %s' % str(answer).replace(u'"', u'') resolver.nameservers = nameservers=[u'8.8.8.8', u'8.8.4.4']
except dns.resolver.NXDOMAIN: try:
print u'ERROR: no such domain found in DNS!' answers = resolver.query(a_domain, u'TXT')
for answer in answers:
print u'DNS Record: %s' % str(answer).replace(u'"', u'')
except dns.resolver.NXDOMAIN:
print u'ERROR: no such domain found in DNS!'
except ImportError:
pass
return return
print u'SUCCESS!' print u'SUCCESS!'
print u'Verified: %s' % verify_result[u'site'][u'identifier'] print u'Verified: %s' % verify_result[u'site'][u'identifier']
@@ -5286,7 +5470,8 @@ def doGetDomainInfo():
if customerId != u'my_customer': if customerId != u'my_customer':
customer_id = customerId customer_id = customerId
else: else:
customer_id = callGAPI(service=cd.users(), function=u'list', fields=u'users(customerId)', customer=customerId, maxResults=1)[u'users'][0][u'customerId'] result = callGAPI(service=cd.users(), function=u'list', fields=u'users(customerId)', customer=customerId, maxResults=1, sortOrder=u'DESCENDING')
customer_id = result[u'users'][0][u'customerId']
print u'Customer ID: %s' % customer_id print u'Customer ID: %s' % customer_id
default_language = callGAPI(service=adm.defaultLanguage(), function=u'get', domainName=domain) default_language = callGAPI(service=adm.defaultLanguage(), function=u'get', domainName=domain)
print u'Default Language: %s' % default_language[u'entry'][u'apps$property'][0][u'value'] print u'Default Language: %s' % default_language[u'entry'][u'apps$property'][0][u'value']
@@ -5523,14 +5708,24 @@ def doPrintUsers():
customer = customerId customer = customerId
domain = None domain = None
query = None query = None
getGroupFeed = getLicenseFeed = False projection = u'basic'
customFieldMask = None
getGroupFeed = getLicenseFeed = email_parts = False
todrive = False todrive = False
deleted_only = orderBy = sortOrder = None viewType = deleted_only = orderBy = sortOrder = None
i = 3 i = 3
while i < len(sys.argv): while i < len(sys.argv):
if sys.argv[i].lower() == u'allfields': if sys.argv[i].lower() == u'allfields':
fields = None fields = None
i += 1 i += 1
elif sys.argv[i].lower() == u'custom':
user_fields.append(u'customSchemas')
if sys.argv[i+1].lower() == u'all':
projection = u'full'
else:
projection = u'custom'
customFieldMask = sys.argv[i+1]
i += 2
elif sys.argv[i].lower() == u'todrive': elif sys.argv[i].lower() == u'todrive':
todrive = True todrive = True
i += 1 i += 1
@@ -5547,6 +5742,9 @@ def doPrintUsers():
elif orderBy.lower() in [u'givenname', u'firstname']: elif orderBy.lower() in [u'givenname', u'firstname']:
orderBy= u'givenName' orderBy= u'givenName'
i += 2 i += 2
elif sys.argv[i].lower() == u'userview':
viewType = u'domain_public'
i += 1
elif sys.argv[i].lower() in [u'ascending', u'descending']: elif sys.argv[i].lower() in [u'ascending', u'descending']:
sortOrder = sys.argv[i].upper() sortOrder = sys.argv[i].upper()
i += 1 i += 1
@@ -5631,7 +5829,7 @@ def doPrintUsers():
fields = u'nextPageToken,users(%s)' % u','.join(user_fields) fields = u'nextPageToken,users(%s)' % u','.join(user_fields)
sys.stderr.write(u"Getting all users in Google Apps account (may take some time on a large account)...\n") sys.stderr.write(u"Getting all users in Google Apps account (may take some time on a large account)...\n")
page_message = u'Got %%total_items%% users: %%first_item%% - %%last_item%%\n' page_message = u'Got %%total_items%% users: %%first_item%% - %%last_item%%\n'
all_users = callGAPIpages(service=cd.users(), function=u'list', items=u'users', page_message=page_message, message_attribute=u'primaryEmail', customer=customer, domain=domain, fields=fields, showDeleted=deleted_only, maxResults=500, orderBy=orderBy, sortOrder=sortOrder, query=query) all_users = callGAPIpages(service=cd.users(), function=u'list', items=u'users', page_message=page_message, message_attribute=u'primaryEmail', customer=customer, domain=domain, fields=fields, showDeleted=deleted_only, maxResults=500, orderBy=orderBy, sortOrder=sortOrder, viewType=viewType, query=query, projection=projection, customFieldMask=customFieldMask)
titles = [] titles = []
attributes = [] attributes = []
for user in all_users: for user in all_users:
@@ -6620,7 +6818,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
if not silent: if not silent:
sys.stderr.write(u"Getting %s of %s (may take some time for large groups)..." % (member_type_message, group)) sys.stderr.write(u"Getting %s of %s (may take some time for large groups)..." % (member_type_message, group))
page_message = u'Got %%%%total_items%%%% %s...' % member_type_message page_message = u'Got %%%%total_items%%%% %s...' % member_type_message
members = callGAPIpages(service=cd.members(), function=u'list', page_message=page_message, groupKey=group, roles=member_type, fields=u'nextPageToken,members(email,id)') members = callGAPIpages(service=cd.members(), function=u'list', items=u'members', page_message=page_message, groupKey=group, roles=member_type, fields=u'nextPageToken,members(email,id)')
users = [] users = []
for member in members: for member in members:
if return_uids: if return_uids:
@@ -6829,11 +7027,14 @@ found at:
%s %s
with information from the APIs Console <https://code.google.com/apis/console>. with information from the APIs Console <https://cloud.google.com/console>.
See https://code.google.com/p/google-apps-manager/wiki/CreatingClientSecretsFile
for instructions.
""" % CLIENT_SECRETS """ % CLIENT_SECRETS
selected_scopes = [u'*'] * 19 selected_scopes = [u'*'] * 20
menu = u'''Select the authorized scopes for this token. Include a 'r' to grant read-only menu = u'''Select the authorized scopes for this token. Include a 'r' to grant read-only
access or an 'a' to grant action-only access. access or an 'a' to grant action-only access.
@@ -6856,10 +7057,11 @@ access or an 'a' to grant action-only access.
[%s] 16) Notifications Directory API [%s] 16) Notifications Directory API
[%s] 17) Site Verification API [%s] 17) Site Verification API
(%s) 18) IMAP/SMTP Access (send notifications to admin) (%s) 18) IMAP/SMTP Access (send notifications to admin)
(%s) 19) User Schemas (supports read-only)
19) Select all scopes 20) Select all scopes
20) Unselect all scopes 21) Unselect all scopes
21) Continue 22) Continue
''' '''
os.system([u'clear', u'cls'][os.name == u'nt']) os.system([u'clear', u'cls'][os.name == u'nt'])
while True: while True:
@@ -6867,7 +7069,7 @@ access or an 'a' to grant action-only access.
try: try:
if selection.lower().find(u'r') != -1: if selection.lower().find(u'r') != -1:
selection = int(selection.lower().replace(u'r', u'')) selection = int(selection.lower().replace(u'r', u''))
if selection not in [0, 1, 2, 3, 4, 10]: if selection not in [0, 1, 2, 3, 4, 10, 19]:
os.system([u'clear', u'cls'][os.name == u'nt']) os.system([u'clear', u'cls'][os.name == u'nt'])
print u'THAT SCOPE DOES NOT SUPPORT READ-ONLY MODE!\n' print u'THAT SCOPE DOES NOT SUPPORT READ-ONLY MODE!\n'
continue continue
@@ -6879,18 +7081,18 @@ access or an 'a' to grant action-only access.
print u'THAT SCOPE DOES NOT SUPPORT ACTION-ONLY MODE!\n' print u'THAT SCOPE DOES NOT SUPPORT ACTION-ONLY MODE!\n'
continue continue
selected_scopes[selection] = u'A' selected_scopes[selection] = u'A'
elif int(selection) > -1 and int(selection) <= 18: elif int(selection) > -1 and int(selection) <= 19:
if selected_scopes[int(selection)] == u' ': if selected_scopes[int(selection)] == u' ':
selected_scopes[int(selection)] = u'*' selected_scopes[int(selection)] = u'*'
else: else:
selected_scopes[int(selection)] = u' ' selected_scopes[int(selection)] = u' '
elif selection == u'19':
for i in range(0, len(selected_scopes)):
selected_scopes[i] = u'*'
elif selection == u'20': elif selection == u'20':
for i in range(0, len(selected_scopes)): for i in range(0, len(selected_scopes)):
selected_scopes[i] = u' ' selected_scopes[i] = u'*'
elif selection == u'21': elif selection == u'21':
for i in range(0, len(selected_scopes)):
selected_scopes[i] = u' '
elif selection == u'22':
at_least_one = False at_least_one = False
for i in range(0, len(selected_scopes)): for i in range(0, len(selected_scopes)):
if selected_scopes[i] in [u'*', u'R', u'A']: if selected_scopes[i] in [u'*', u'R', u'A']:
@@ -6929,7 +7131,8 @@ access or an 'a' to grant action-only access.
u'https://www.googleapis.com/auth/admin.directory.user.security', # User Security Directory API u'https://www.googleapis.com/auth/admin.directory.user.security', # User Security Directory API
u'https://www.googleapis.com/auth/admin.directory.notifications', # Notifications Directory API u'https://www.googleapis.com/auth/admin.directory.notifications', # Notifications Directory API
u'https://www.googleapis.com/auth/siteverification', # Site Verification API u'https://www.googleapis.com/auth/siteverification', # Site Verification API
u'https://mail.google.com/'] # IMAP/SMTP authentication for admin notifications u'https://mail.google.com/', # IMAP/SMTP authentication for admin notifications
u'https://www.googleapis.com/auth/admin.directory.userschema'] # Customer User Schema
if incremental_auth: if incremental_auth:
scopes = [] scopes = []
else: else:
@@ -7067,12 +7270,14 @@ try:
doCreateGroup() doCreateGroup()
elif sys.argv[2].lower() in [u'nickname', u'alias']: elif sys.argv[2].lower() in [u'nickname', u'alias']:
doCreateAlias() doCreateAlias()
elif sys.argv[2].lower() == u'org': elif sys.argv[2].lower() in [u'org', 'ou']:
doCreateOrg() doCreateOrg()
elif sys.argv[2].lower() == u'resource': elif sys.argv[2].lower() == u'resource':
doCreateResource() doCreateResource()
elif sys.argv[2].lower() in [u'verify', u'verification']: elif sys.argv[2].lower() in [u'verify', u'verification']:
doSiteVerifyShow() doSiteVerifyShow()
elif sys.argv[2].lower() in [u'schema']:
doCreateOrUpdateUserSchema()
else: else:
print u'Error: invalid argument to "gam create..."' print u'Error: invalid argument to "gam create..."'
sys.exit(2) sys.exit(2)
@@ -7098,6 +7303,8 @@ try:
doUpdateNotification() doUpdateNotification()
elif sys.argv[2].lower() in [u'verify', u'verification']: elif sys.argv[2].lower() in [u'verify', u'verification']:
doSiteVerifyAttempt() doSiteVerifyAttempt()
elif sys.argv[2].lower() in [u'schema', u'schemas']:
doCreateOrUpdateUserSchema()
else: else:
showUsage() showUsage()
print u'Error: invalid argument to "gam update..."' print u'Error: invalid argument to "gam update..."'
@@ -7124,6 +7331,8 @@ try:
doGetNotifications() doGetNotifications()
elif sys.argv[2].lower() in [u'verify', u'verification']: elif sys.argv[2].lower() in [u'verify', u'verification']:
doGetSiteVerifications() doGetSiteVerifications()
elif sys.argv[2].lower() in [u'schema', u'schemas']:
doGetUserSchema()
else: else:
print u'Error: invalid argument to "gam info..."' print u'Error: invalid argument to "gam info..."'
sys.exit(2) sys.exit(2)
@@ -7145,6 +7354,8 @@ try:
doDeleteNotification() doDeleteNotification()
elif sys.argv[2].lower() in [u'verify', u'verification']: elif sys.argv[2].lower() in [u'verify', u'verification']:
doDelSiteVerify() doDelSiteVerify()
elif sys.argv[2].lower() in [u'schema', u'schemas']:
doDelSchema()
else: else:
print u'Error: invalid argument to "gam delete"' print u'Error: invalid argument to "gam delete"'
sys.exit(2) sys.exit(2)
@@ -7222,6 +7433,8 @@ try:
doPrintLicenses() doPrintLicenses()
elif sys.argv[2].lower() in [u'token', u'tokens']: elif sys.argv[2].lower() in [u'token', u'tokens']:
doPrintTokens() doPrintTokens()
elif sys.argv[2].lower() in [u'schema', u'schemas']:
doPrintUserSchemas()
else: else:
print u'Error: invalid argument to "gam print..."' print u'Error: invalid argument to "gam print..."'
sys.exit(2) sys.exit(2)

View File

@@ -1269,7 +1269,7 @@ class OAuth2WebServerFlow(Flow):
'client_secret': self.client_secret, 'client_secret': self.client_secret,
'code': code, 'code': code,
'redirect_uri': self.redirect_uri, 'redirect_uri': self.redirect_uri,
'scope': self.scope, # 'scope': self.scope,
}) })
headers = { headers = {
'content-type': 'application/x-www-form-urlencoded', 'content-type': 'application/x-www-form-urlencoded',

View File

@@ -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 re
import urllib try:
from urllib.parse import quote
except ImportError:
from urllib import quote
__version__ = "0.6"
RESERVED = ":/?#[]@!$&'()*+,;=" RESERVED = ":/?#[]@!$&'()*+,;="
OPERATOR = "+./;?|!@" OPERATOR = "+#./;?&|!@"
EXPLODE = "*+"
MODIFIER = ":^" MODIFIER = ":^"
TEMPLATE = re.compile(r"{(?P<operator>[\+\./;\?|!@])?(?P<varlist>[^}]+)}", re.UNICODE) TEMPLATE = re.compile("{([^\}]+)}")
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)
def _tostring_path(varname, value, explode, operator, safe=""): def variables(template):
joiner = operator '''Returns the set of keywords in a uri template'''
if type(value) == type([]): vars = set()
if explode == "+": for varlist in TEMPLATE.findall(template):
return joiner.join([varname + "." + urllib.quote(x, safe) for x in value]) if varlist[0] in OPERATOR:
elif explode == "*": varlist = varlist[1:]
return joiner.join([urllib.quote(x, safe) for x in value]) varspecs = varlist.split(',')
else: for var in varspecs:
return ",".join([urllib.quote(x, safe) for x in value]) # handle prefix values
elif type(value) == type({}): var = var.split(':')[0]
keys = value.keys() # handle composite values
keys.sort() if var.endswith('*'):
if explode == "+": var = var[:-1]
return joiner.join([varname + "." + urllib.quote(key, safe) + joiner + urllib.quote(value[key], safe) for key in keys]) vars.add(var)
elif explode == "*": return vars
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 _tostring_query(varname, value, explode, operator, safe=""):
joiner = operator def _quote(value, safe, prefix=None):
varprefix = "" if prefix is not None:
if operator == "?": return quote(str(value)[:prefix], safe)
joiner = "&" return quote(str(value), safe)
varprefix = varname + "="
if type(value) == type([]):
if 0 == len(value): def _tostring(varname, value, explode, prefix, operator, safe=""):
return "" if isinstance(value, list):
if explode == "+": return ",".join([_quote(x, safe) for x in value])
return joiner.join([varname + "=" + urllib.quote(x, safe) for x in value]) if isinstance(value, dict):
elif explode == "*": keys = sorted(value.keys())
return joiner.join([urllib.quote(x, safe) for x in value]) 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: else:
return varprefix + ",".join([urllib.quote(x, safe) for x in value]) return _quote(value, safe, prefix)
elif type(value) == type({}):
if 0 == len(value):
return "" def _tostring_path(varname, value, explode, prefix, operator, safe=""):
keys = value.keys() joiner = operator
keys.sort() if isinstance(value, list):
if explode == "+": if explode:
return joiner.join([varname + "." + urllib.quote(key, safe) + "=" + urllib.quote(value[key], safe) for key in keys]) out = [_quote(x, safe) for x in value if value is not None]
elif explode == "*": else:
return joiner.join([urllib.quote(key, safe) + "=" + urllib.quote(value[key], safe) for key in keys]) 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: else:
return varprefix + ",".join([urllib.quote(key, safe) + "," + urllib.quote(value[key], safe) for key in keys]) return _quote(value, safe, prefix)
else:
if value:
return varname + "=" + urllib.quote(value, safe) 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: 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,
"+": _tostring, "+": _tostring,
";": _tostring_query, "#": _tostring,
";": _tostring_semi,
"?": _tostring_query, "?": _tostring_query,
"&": _tostring_query,
"/": _tostring_path, "/": _tostring_path,
".": _tostring_path, ".": _tostring_path,
} }
def expand(template, vars): def expand(template, variables):
def _sub(match): """
groupdict = match.groupdict() Expand template as a URI Template using variables.
operator = groupdict.get('operator') """
if operator is None: def _sub(match):
operator = '' expression = match.group(1)
varlist = groupdict.get('varlist') operator = ""
if expression[0] in OPERATOR:
operator = expression[0]
varlist = expression[1:]
else:
varlist = expression
safe = "@" safe = ""
if operator == '+': if operator in ["+", "#"]:
safe = RESERVED safe = RESERVED
varspecs = varlist.split(",") varspecs = varlist.split(",")
varnames = [] varnames = []
defaults = {} defaults = {}
for varspec in varspecs: for varspec in varspecs:
m = VAR.search(varspec) default = None
groupdict = m.groupdict() explode = False
varname = groupdict.get('varname') prefix = None
explode = groupdict.get('explode') if "=" in varspec:
partial = groupdict.get('partial') varname, default = tuple(varspec.split("=", 1))
default = groupdict.get('default') else:
if default: varname = varspec
defaults[varname] = default if varname[-1] == "*":
varnames.append((varname, explode, partial)) 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 = [] retval = []
joiner = operator joiner = operator
prefix = operator start = operator
if operator == "+": if operator == "+":
prefix = "" start = ""
joiner = "," joiner = ","
if operator == "?": if operator == "#":
joiner = "&" joiner = ","
if operator == "": if operator == "?":
joiner = "," joiner = "&"
for varname, explode, partial in varnames: if operator == "&":
if varname in vars: start = "&"
value = vars[varname] if operator == "":
#if not value and (type(value) == type({}) or type(value) == type([])) and varname in defaults: joiner = ","
if not value and value != "" and varname in defaults: for varname, explode, prefix in varnames:
value = defaults[varname] if varname in variables:
elif varname in defaults: value = variables[varname]
value = defaults[varname] if not value and value != "" and varname in defaults:
else: value = defaults[varname]
continue elif varname in defaults:
retval.append(TOSTRING[operator](varname, value, explode, operator, safe=safe)) value = defaults[varname]
if "".join(retval): else:
return prefix + joiner.join(retval) continue
else: expanded = TOSTRING[operator](
return "" 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)

View File

@@ -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 GAM 3.21
-Fix crash when attempting to perform Drive operations for users with Drive service disabled. -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. -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. -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.