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-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

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>'
__version__ = u'3.32'
__version__ = u'3.4'
__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
@@ -447,17 +447,17 @@ def callGAPIpages(service, function, items=u'items', nextPageToken=u'nextPageTok
show_message = page_message
try:
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')
try:
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')
if message_attribute:
try:
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]))
except KeyError:
except (IndexError, KeyError):
show_message = show_message.replace(u'%%first_item%%', '')
show_message = show_message.replace(u'%%last_item%%', '')
restart_line()
@@ -467,7 +467,7 @@ def callGAPIpages(service, function, items=u'items', nextPageToken=u'nextPageTok
pageToken = this_page[nextPageToken]
if pageToken == '':
return all_pages
except KeyError:
except (IndexError, KeyError):
return all_pages
def getAPIVer(api):
@@ -2214,6 +2214,7 @@ def downloadDriveFile(users):
query = fileIds = None
gdownload_format = u'openoffice'
target_folder = getGamPath()
safe_filename_chars = "-_.() %s%s" % (string.ascii_letters, string.digits)
while i < len(sys.argv):
if sys.argv[i].lower() == u'id':
fileIds = [sys.argv[i+1],]
@@ -2263,7 +2264,7 @@ def downloadDriveFile(users):
if fileIds[0].find('/') != -1:
fileIds[0] = fileIds[0][:fileIds[0].find('/')]
if not fileIds:
print u'No files to delete for %s' % user
print u'No files to download for %s' % user
i = 0
for fileId in fileIds:
extension = None
@@ -2295,7 +2296,9 @@ def downloadDriveFile(users):
except KeyError:
pass
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:
filename = u'%s%s' % (filename, extension)
y = 0
@@ -2664,7 +2667,7 @@ def showSendAs(users):
print u''
def doLanguage(users):
language = sys.argv[4].lower()
language = sys.argv[4]
emailsettings = getEmailSettingsObject()
count = len(users)
i = 1
@@ -3270,6 +3273,104 @@ def getVacation(users):
except TypeError:
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():
cd = buildGAPIObject(u'directory')
body = dict()
@@ -3567,9 +3668,33 @@ def doCreateUser():
except KeyError:
body[u'externalIds'] = [externalid,]
i += 1
# else:
# showUsage()
# sys.exit(2)
else:
showUsage()
sys.exit(2)
if u'customSchemas' not in body:
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:
body[u'name'][u'givenName'] = u'Unknown'
if not gotLastName:
@@ -4039,11 +4164,36 @@ def doUpdateUser(users):
except KeyError:
body[u'externalIds'] = [externalid,]
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:
showUsage()
print u''
print u'Error: didn\'t expect %s command at position %s' % (sys.argv[i], i)
sys.exit(2)
do_update_user = True
if u'customSchemas' not in body:
body[u'customSchemas'] = {}
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):
body[u'password'] = gen_sha512_hash(body[u'password'])
body[u'hashFunction'] = u'crypt'
@@ -4185,7 +4335,7 @@ def doUpdateGroup():
gs_object = buildDiscoveryObject(u'groupssettings')
matches_gs_setting = False
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
if sys.argv[i].lower().replace(u'_', u'') == attrib.lower():
matches_gs_setting = True
@@ -4211,7 +4361,7 @@ def doUpdateGroup():
value = u'false'
break
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)
gs_body[attrib] = value
use_gs_api = True
@@ -4472,7 +4622,9 @@ def doGetUserInfo(user_email=None):
user_email = user_email[4:]
elif user_email.find(u'@') == -1:
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
while i < len(sys.argv):
if sys.argv[i].lower() == u'noaliases':
@@ -4484,10 +4636,23 @@ def doGetUserInfo(user_email=None):
elif sys.argv[i].lower() == u'nolicenses':
getLicenses = False
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:
print u'%s is not a valid argument for gam info user' % sys.argv[i]
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']
if u'name' in user and u'givenName' in user[u'name']:
print u'First Name: %s' % user[u'name'][u'givenName']
@@ -4586,6 +4751,19 @@ def doGetUserInfo(user_email=None):
else:
print u' %s: %s' % (key, id[key])
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 u'aliases' in user:
print u'Email Aliases:'
@@ -4860,28 +5038,34 @@ def doSiteVerifyAttempt():
print u'Method: %s' % verify_data[u'method']
print u'Token: %s' % verify_data[u'token']
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:
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:
print u'ERROR: No such domain found in DNS!'
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:
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':
import dns.resolver
resolver = dns.resolver.Resolver()
resolver.nameservers = nameservers=[u'8.8.8.8', u'8.8.4.4']
try:
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!'
import dns.resolver
resolver = dns.resolver.Resolver()
resolver.nameservers = nameservers=[u'8.8.8.8', u'8.8.4.4']
try:
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
print u'SUCCESS!'
print u'Verified: %s' % verify_result[u'site'][u'identifier']
@@ -5286,7 +5470,8 @@ def doGetDomainInfo():
if customerId != u'my_customer':
customer_id = customerId
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
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']
@@ -5523,14 +5708,24 @@ def doPrintUsers():
customer = customerId
domain = None
query = None
getGroupFeed = getLicenseFeed = False
projection = u'basic'
customFieldMask = None
getGroupFeed = getLicenseFeed = email_parts = False
todrive = False
deleted_only = orderBy = sortOrder = None
viewType = deleted_only = orderBy = sortOrder = None
i = 3
while i < len(sys.argv):
if sys.argv[i].lower() == u'allfields':
fields = None
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':
todrive = True
i += 1
@@ -5547,6 +5742,9 @@ def doPrintUsers():
elif orderBy.lower() in [u'givenname', u'firstname']:
orderBy= u'givenName'
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']:
sortOrder = sys.argv[i].upper()
i += 1
@@ -5631,7 +5829,7 @@ def doPrintUsers():
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")
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 = []
attributes = []
for user in all_users:
@@ -6620,7 +6818,7 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
if not silent:
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
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 = []
for member in members:
if return_uids:
@@ -6829,11 +7027,14 @@ found at:
%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
selected_scopes = [u'*'] * 19
selected_scopes = [u'*'] * 20
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.
@@ -6856,10 +7057,11 @@ access or an 'a' to grant action-only access.
[%s] 16) Notifications Directory API
[%s] 17) Site Verification API
(%s) 18) IMAP/SMTP Access (send notifications to admin)
(%s) 19) User Schemas (supports read-only)
19) Select all scopes
20) Unselect all scopes
21) Continue
20) Select all scopes
21) Unselect all scopes
22) Continue
'''
os.system([u'clear', u'cls'][os.name == u'nt'])
while True:
@@ -6867,7 +7069,7 @@ access or an 'a' to grant action-only access.
try:
if selection.lower().find(u'r') != -1:
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'])
print u'THAT SCOPE DOES NOT SUPPORT READ-ONLY MODE!\n'
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'
continue
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' ':
selected_scopes[int(selection)] = u'*'
else:
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':
for i in range(0, len(selected_scopes)):
selected_scopes[i] = u' '
selected_scopes[i] = u'*'
elif selection == u'21':
for i in range(0, len(selected_scopes)):
selected_scopes[i] = u' '
elif selection == u'22':
at_least_one = False
for i in range(0, len(selected_scopes)):
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.notifications', # Notifications Directory 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:
scopes = []
else:
@@ -7067,12 +7270,14 @@ try:
doCreateGroup()
elif sys.argv[2].lower() in [u'nickname', u'alias']:
doCreateAlias()
elif sys.argv[2].lower() == u'org':
elif sys.argv[2].lower() in [u'org', 'ou']:
doCreateOrg()
elif sys.argv[2].lower() == u'resource':
doCreateResource()
elif sys.argv[2].lower() in [u'verify', u'verification']:
doSiteVerifyShow()
elif sys.argv[2].lower() in [u'schema']:
doCreateOrUpdateUserSchema()
else:
print u'Error: invalid argument to "gam create..."'
sys.exit(2)
@@ -7098,6 +7303,8 @@ try:
doUpdateNotification()
elif sys.argv[2].lower() in [u'verify', u'verification']:
doSiteVerifyAttempt()
elif sys.argv[2].lower() in [u'schema', u'schemas']:
doCreateOrUpdateUserSchema()
else:
showUsage()
print u'Error: invalid argument to "gam update..."'
@@ -7124,6 +7331,8 @@ try:
doGetNotifications()
elif sys.argv[2].lower() in [u'verify', u'verification']:
doGetSiteVerifications()
elif sys.argv[2].lower() in [u'schema', u'schemas']:
doGetUserSchema()
else:
print u'Error: invalid argument to "gam info..."'
sys.exit(2)
@@ -7145,6 +7354,8 @@ try:
doDeleteNotification()
elif sys.argv[2].lower() in [u'verify', u'verification']:
doDelSiteVerify()
elif sys.argv[2].lower() in [u'schema', u'schemas']:
doDelSchema()
else:
print u'Error: invalid argument to "gam delete"'
sys.exit(2)
@@ -7222,6 +7433,8 @@ try:
doPrintLicenses()
elif sys.argv[2].lower() in [u'token', u'tokens']:
doPrintTokens()
elif sys.argv[2].lower() in [u'schema', u'schemas']:
doPrintUserSchemas()
else:
print u'Error: invalid argument to "gam print..."'
sys.exit(2)

View File

@@ -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',

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 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)

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
-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.