diff --git a/src/gam.py b/src/gam.py index fc078f75..cd156c6e 100755 --- a/src/gam.py +++ b/src/gam.py @@ -454,7 +454,7 @@ def callGAPI(service, function, silent_errors=False, soft_errors=False, throw_re reason = http_status if reason in throw_reasons: raise e - if n != retries and (reason in [u'rateLimitExceeded', u'userRateLimitExceeded', u'backendError', u'internalError'] or reason in retry_reasons): + if n != retries and (reason in [u'quotaExceeded', u'rateLimitExceeded', u'userRateLimitExceeded', u'backendError', u'internalError'] or reason in retry_reasons): wait_on_fail = (2 ** n) if (2 ** n) < 60 else 60 randomness = float(random.randint(1, 1000)) / 1000 wait_on_fail = wait_on_fail + randomness @@ -683,10 +683,6 @@ def getEmailSettingsObject(): import gdata.apps.emailsettings.service return commonAppsObjInit(gdata.apps.emailsettings.service.EmailSettingsService()) -def getResCalObject(): - import gdata.apps.res_cal.service - return commonAppsObjInit(gdata.apps.res_cal.service.ResCalService()) - def geturl(url, dst): import urllib2 u = urllib2.urlopen(url) @@ -1312,6 +1308,153 @@ def doPrintDomains(): domains_attributes.append(aliasdomain_attributes) output_csv(domains_attributes, titles, u'Domains', todrive) +def doDelAdmin(): + cd = buildGAPIObject(u'directory') + roleAssignmentId = sys.argv[3] + print u'Deleting Admin Role Assignment %s' % roleAssignmentId + callGAPI(service=cd.roleAssignments(), function=u'delete', + customer=customerId, roleAssignmentId=roleAssignmentId) + +def doCreateAdmin(): + cd = buildGAPIObject(u'directory') + body = {} + user = sys.argv[3] + if user[:4].lower() == u'uid:': + body[u'assignedTo'] = user[4:] + else: + print user[:3] + body[u'assignedTo'] = callGAPI(service=cd.users(), function=u'get', userKey=user, + projection=u'basic', fields=u'id')[u'id'] + role = sys.argv[4] + if role[:4].lower() == u'uid:': + body[u'roleId'] = role[4:] + else: + body[u'roleId'] = roleid_from_role(role) + if not body[u'roleId']: + print u'ERROR: %s is not a valid role. Please ensure role name is exactly as shown in admin console.' % role + sys.exit(4) + body[u'scopeType'] = sys.argv[5].upper() + if body[u'scopeType'] not in [u'CUSTOMER', u'ORG_UNIT']: + print u'ERROR: scope type must be customer or org_unit, got %s' % body[u'scopeType'] + sys.exit(3) + if body[u'scopeType'] == u'ORG_UNIT': + orgUnit = sys.argv[6] + if orgUnit[:4] == u'uid:': + body[u'orgUnitId'] = orgUnit[4:] + else: + if orgUnit[0] == u'/': + orgUnit = orgUnit[1:] + body[u'orgUnitId'] = callGAPI(service=cd.orgunits(), function=u'get', + customerId=customerId, orgUnitPath=orgUnit, + fields=u'orgUnitId')[u'orgUnitId'][3:] + if body[u'scopeType'] == u'CUSTOMER': + scope = u'customer' + else: + scope = orgUnit + print u'Giving %s admin role %s for %s' % (user, role, scope) + callGAPI(service=cd.roleAssignments(), function=u'insert', + customer=customerId, body=body) + +def doPrintAdmins(): + cd = buildGAPIObject(u'directory') + roleId = None + userKey = None + i = 3 + while i < len(sys.argv): + if sys.argv[i].lower() == u'user': + userKey = sys.argv[i+1] + i += 2 + elif sys.argv[i].lower() == u'role': + role = sys.argv[i+1] + if role[:4].lower() == u'uid:': + roleId = role[4:] + else: + roleId = roleid_from_role(role) + if not roleId: + print u'ERROR: %s is not a valid role' % role + sys.exit(5) + i += 2 + admins = callGAPIpages(service=cd.roleAssignments(), function=u'list', + customer=customerId, maxResults=200, userKey=userKey, roleId=roleId) + admins_attrib = [{}] + for admin in admins: + admin_attrib = {} + for key, value in admin.items(): + if key in [u'kind', u'etag']: + continue + if key not in admins_attrib[0]: + admins_attrib[0][key] = key + admin_attrib[key] = value + if key == u'assignedTo': + assignedToUser = user_from_userid(value) + if u'assignedToUser' not in admins_attrib[0]: + admins_attrib[0][u'assignedToUser'] = u'assignedToUser' + admin_attrib[u'assignedToUser'] = assignedToUser + elif key == u'roleId': + role = role_from_roleid(value) + if u'role' not in admins_attrib[0]: + admins_attrib[0][u'role'] = u'role' + admin_attrib[u'role'] = role + elif key == u'orgUnitId': + orgUnit = orgunit_from_orgunitid(value) + if u'orgUnit' not in admins_attrib[0]: + admins_attrib[0][u'orgUnit'] = u'orgUnit' + admin_attrib[u'orgUnit'] = orgUnit + admins_attrib.append(admin_attrib) + output_csv(admins_attrib, admins_attrib[0], u'Admins', False) + +def orgunit_from_orgunitid(orgunitid): + global orgunit_mappings + try: + orgunit_mappings + except NameError: + cd = buildGAPIObject(u'directory') + orgunit_mappings = callGAPI(service=cd.orgunits(), function=u'list', + customerId=customerId, + fields=u'organizationUnits(orgUnitPath,orgUnitId)') + for orgunit_mapping in orgunit_mappings[u'organizationUnits']: + if orgunit_mapping[u'orgUnitId'] == u'id:%s' % orgunitid: + return orgunit_mapping[u'orgUnitPath'] + +def role_from_roleid(roleid): + global roleid_mappings + try: + roleid_mappings + except NameError: + cd = buildGAPIObject(u'directory') + roleid_mappings = callGAPIpages(service=cd.roles(), function=u'list', + items=u'items', customer=customerId, maxResults=100, + fields=u'nextPageToken,items(roleId,roleName)') + for roleid_mapping in roleid_mappings: + if roleid_mapping[u'roleId'] == roleid: + return roleid_mapping[u'roleName'] + +def roleid_from_role(role): + global roleid_mappings + try: + roleid_mappings + except NameError: + cd = buildGAPIObject(u'directory') + roleid_mappings = callGAPIpages(service=cd.roles(), function=u'list', + items=u'items', customer=customerId, maxResults=100, + fields=u'nextPageToken,items(roleId,roleName)') + for roleid_mapping in roleid_mappings: + if roleid_mapping[u'roleName'] == role: + return roleid_mapping[u'roleId'] + +def user_from_userid(userid): + global userid_mappings + try: + userid_mappings + except NameError: + cd = buildGAPIObject(u'directory') + userid_mappings = callGAPIpages(service=cd.users(), function=u'list', + items=u'users', customer=customerId, maxResults=500, + fields=u'nextPageToken,users(id,primaryEmail)') + for user in userid_mappings: + if user[u'id'] == userid: + return user[u'primaryEmail'] + SERVICE_NAME_TO_ID_MAP = { u'Drive': u'55656082996', u'Google+': '553547912911', @@ -4057,23 +4200,6 @@ def doDeleteMessages(trashOrDelete, users): print u'WARNING: refusing to delete ANY messages for %s since max_to_delete is %s and messages to be deleted is %s\n' % (user, maxToDelete, del_count) continue i = 1 - # Batch seemed like a good idea but it kills - # Gmail UI for users :-( - '''dbatch = googleapiclient.http.BatchHttpRequest() - for del_me in listResult: - print u' deleting message %s for user %s (%s/%s)' % (del_me[u'id'], user, i, del_count) - i += 1 - if trashOrDelete == u'trash': - dbatch.add(gmail.users().messages().trash(userId=u'me', - id=del_me[u'id']), callback=gmail_del_result) - elif trashOrDelete == u'delete': - dbatch.add(gmail.users().messages().delete(userId=u'me', - id=del_me[u'id']), callback=gmail_del_result) - if len(dbatch._order) == 5: - dbatch.execute() - dbatch = googleapiclient.http.BatchHttpRequest() - if len(dbatch._order) > 0: - dbatch.execute()''' for del_me in listResult: print u' %s message %s for user %s (%s/%s)' % (trashOrDelete, del_me[u'id'], user, i, del_count) i += 1 @@ -5084,23 +5210,23 @@ def doCreateOrg(): callGAPI(service=cd.orgunits(), function=u'insert', customerId=customerId, body=body) def doCreateResource(): - resId = sys.argv[3] - common_name = sys.argv[4] - description = None - resType = None + body = {u'resourceId': sys.argv[3], + u'resourceName': sys.argv[4]} i = 5 while i < len(sys.argv): if sys.argv[i].lower() == u'description': - description = sys.argv[i+1] + body[u'resourceDescription'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'type': - resType = sys.argv[i+1] + body[u'resourceType'] = sys.argv[i+1] i += 2 else: print u'ERROR: %s is not a valid argument for "gam create resource"' % sys.argv[i] sys.exit(2) - rescal = getResCalObject() - callGData(service=rescal, function=u'CreateResourceCalendar', id=resId, common_name=common_name, description=description, type=resType) + cd = buildGAPIObject(u'directory') + print u'Creating resource %s...' % body[u'resourceId'] + callGAPI(service=cd.resources().calendars(), function=u'insert', + customer=customerId, body=body) def doUpdateUser(users): cd = buildGAPIObject(u'directory') @@ -5694,25 +5820,27 @@ def doUpdateAlias(): def doUpdateResourceCalendar(): resId = sys.argv[3] - common_name = None - description = None - resType = None + body = {} i = 4 while i < len(sys.argv): if sys.argv[i].lower() == u'name': - common_name = sys.argv[i+1] + body[u'resourceName'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'description': - description = sys.argv[i+1] + body[u'resourceDescription'] = sys.argv[i+1] i += 2 elif sys.argv[i].lower() == u'type': - resType = sys.argv[i+1] + body[u'resourceType'] = sys.argv[i+1] i += 2 else: print u'ERROR: %s is not a valid argument for "gam update resource"' % sys.argv[i] sys.exit(2) - rescal = getResCalObject() - callGData(service=rescal, function=u'UpdateResourceCalendar', id=resId, common_name=common_name, description=description, type=resType) + cd = buildGAPIObject(u'directory') + # Use patch since it seems to work better. + # update requires name to be set. + callGAPI(service=cd.resources().calendars(), function=u'patch', + customer=customerId, calendarResourceId=resId, body=body, + fields=u'') print u'updated resource %s' % resId def doUpdateCros(): @@ -6162,19 +6290,13 @@ def doGetAliasInfo(alias_email=None): def doGetResourceCalendarInfo(): resId = sys.argv[3] - rescal = getResCalObject() - result = callGData(service=rescal, function=u'RetrieveResourceCalendar', id=resId) - print u' Resource ID: '+result[u'resourceId'] - print u' Name: '+result[u'resourceCommonName'] - print u' Email: '+result[u'resourceEmail'] - try: - print u' Type: '+result[u'resourceType'] - except KeyError: - print u' Type: ' - try: - print u' Description: '+result[u'resourceDescription'] - except KeyError: - print u' Description: ' + cd = buildGAPIObject(u'directory') + resource = callGAPI(service=cd.resources().calendars(), function=u'get', + customer=customerId, calendarResourceId=resId) + for key, value in resource.items(): + if key in [u'kind', u'etag', u'etags']: + continue + print u'%s: %s' % (key, value) def doGetCrosInfo(): deviceId = sys.argv[3] @@ -6925,9 +7047,10 @@ def doDeleteAlias(alias_email=None): def doDeleteResourceCalendar(): res_id = sys.argv[3] - rescal = getResCalObject() + cd = buildGAPIObject(u'directory') print u"Deleting resource calendar %s" % res_id - callGData(service=rescal, function=u'DeleteResourceCalendar', id=res_id) + callGAPI(service=cd.resources().calendars(), function=u'delete', + calendarResourceId=res_id, customer=customerId) def doDeleteOrg(): name = sys.argv[3] @@ -7771,65 +7894,48 @@ def doPrintTokens(): def doPrintResources(): i = 3 - res_attributes = [] - res_attributes.append({u'Name': u'Name'}) - titles = ['Name'] - printid = printdesc = printemail = printtype = todrive = False + todrive = False + fields = [u'resourceId', u'resourceName', u'resourceEmail'] while i < len(sys.argv): if sys.argv[i].lower() == u'allfields': - printid = printdesc = printemail = True - res_attributes[0].update(ID=u'ID', Description=u'Description', Email=u'Email', Type=u'Type') - titles.append(u'ID') - titles.append(u'Description') - titles.append(u'Email') - titles.append(u'Type') + fields = None i += 1 elif sys.argv[i].lower() == u'todrive': todrive = True i += 1 elif sys.argv[i].lower() == u'id': - printid = True - res_attributes[0].update(ID=u'ID') - titles.append(u'ID') i += 1 elif sys.argv[i].lower() == u'description': - printdesc = True - res_attributes[0].update(Description=u'Description') - titles.append(u'Description') + fields.append(u'resourceDescription') i += 1 elif sys.argv[i].lower() == u'email': - printemail = True - res_attributes[0].update(Email=u'Email') - titles.append(u'Email') i += 1 elif sys.argv[i].lower() == u'type': - printtype = True - res_attributes[0].update(Type=u'Type') - titles.append(u'Type') + fields.append(u'resourceType') i += 1 else: print 'ERROR: %s is not a valid argument for "gam print resources"' % sys.argv[i] sys.exit(2) - resObj = getResCalObject() + if fields: + fields = u'nextPageToken,items(%s)' % u','.join(fields) + cd = buildGAPIObject(u'directory') sys.stderr.write(u"Retrieving All Resource Calendars for your account (may take some time on a large domain)\n") - resources = callGData(service=resObj, function=u'RetrieveAllResourceCalendars') + page_message = u'Got %%total_items%% resources: %%first_item%% - %%last_item%%\n' + resources = callGAPIpages(service=cd.resources().calendars(), + function=u'list', items=u'items', fields=fields, + page_message=page_message, message_attribute=u'resourceId', + maxResults=500, customer=customerId) + resources_attrib = [{u'resourceId':u'resourceId', + u'resourceEmail': u'resourceEmail', + u'resourceName': u'resourceName'}] for resource in resources: - resUnit = {} - resUnit.update({u'Name': resource[u'resourceCommonName']}) - if printid: - resUnit.update({u'ID': resource[u'resourceId']}) - if printdesc: - try: - desc = resource[u'resourceDescription'] - except KeyError: - desc = '' - resUnit.update({u'Description': desc}) - if printemail: - resUnit.update({u'Email': resource[u'resourceEmail']}) - if printtype: - resUnit.update({u'Type': resource[u'resourceType']}) - res_attributes.append(resUnit) - output_csv(res_attributes, titles, u'Resources', todrive) + resource_attrib = {} + for key, value in resource.items(): + if key not in resources_attrib[0]: + resources_attrib[0][key] = key + resource_attrib[key] = value + resources_attrib.append(resource_attrib) + output_csv(resources_attrib, resources_attrib[0], u'Resources', todrive) def doCreateMonitor(): source_user = sys.argv[4].lower() @@ -8419,7 +8525,7 @@ possible_scopes = [u'https://www.googleapis.com/auth/admin.directory.group', u'https://www.googleapis.com/auth/admin.directory.device.chromeos', # Chrome OS Devices Directory Scope u'https://www.googleapis.com/auth/admin.directory.device.mobile', # Mobile Device Directory Scope u'https://apps-apis.google.com/a/feeds/emailsettings/2.0/', # Email Settings API - u'https://apps-apis.google.com/a/feeds/calendar/resource/', # Calendar Resource API + u'https://www.googleapis.com/auth/admin.directory.resource.calendar',# Calendar Resource API u'https://apps-apis.google.com/a/feeds/compliance/audit/', # Email Audit API u'https://apps-apis.google.com/a/feeds/domain/', # Admin Settings API u'https://www.googleapis.com/auth/apps.groups.settings', # Group Settings API @@ -8437,7 +8543,8 @@ possible_scopes = [u'https://www.googleapis.com/auth/admin.directory.group', u'https://www.googleapis.com/auth/cloudprint', # CloudPrint API u'https://www.googleapis.com/auth/admin.datatransfer', # Data Transfer API u'https://www.googleapis.com/auth/admin.directory.customer', # Customer API - u'https://www.googleapis.com/auth/admin.directory.domain'] # Domain API + u'https://www.googleapis.com/auth/admin.directory.domain', # Domain API + u'https://www.googleapis.com/auth/admin.directory.rolemanagement'] # Roles API def doRequestOAuth(incremental_auth=False): CLIENT_SECRETS = os.path.join(gamUserConfigDir, os.environ.get(u'CLIENTSECRETSFILE', FN_CLIENT_SECRETS_JSON)) @@ -8468,7 +8575,7 @@ access or an 'a' to grant action-only access. [%%s] %s) Chrome OS Device Directory API (supports read-only) [%%s] %s) Mobile Device Directory API (supports read-only and action) [%%s] %s) User Email Settings API -[%%s] %s) Calendar Resources API +[%%s] %s) Calendar Resources API (supports read-only [%%s] %s) Audit Monitors, Activity and Mailbox Exports API [%%s] %s) Admin Settings API [%%s] %s) Groups Settings API @@ -8487,12 +8594,15 @@ access or an 'a' to grant action-only access. [%%s] %s) Data Transfer API (supports read-only) [%%s] %s) Customer Directory API (supports read-only) [%%s] %s) Domains Directory API (supports read-only) +[%%s] %s) Roles API (supports read-only) %%s) Select all scopes %%s) Unselect all scopes %%s) Continue ''' % tuple(range(0, num_scopes)) selected_scopes = [u'*'] * num_scopes + selected_scopes[16] = u' ' + # turn off notifications API by default to prevent 500 due to scope length select_all_scopes = unicode(str(num_scopes)) unselect_all_scopes = unicode(str(num_scopes+1)) authorize_scopes = unicode(str(num_scopes+2)) @@ -8505,7 +8615,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, 19, 22, 23, 24]: + if selection not in [0, 1, 2, 3, 4, 6, 10, 19, 22, 23, 24, 25]: os.system([u'clear', u'cls'][os.name == u'nt']) print u'THAT SCOPE DOES NOT SUPPORT READ-ONLY MODE!\n' continue @@ -8636,6 +8746,9 @@ try: run_batch(items) sys.exit(0) elif sys.argv[1].lower() == u'csv': + if httplib2.debuglevel > 0: + print u'Sorry, CSV commands are not compatible with debug. Delete debug.gam and try again.' + sys.exit(1) csv_filename = sys.argv[2] f = openFile(csv_filename) input_file = csv.DictReader(f) @@ -8643,10 +8756,17 @@ try: print 'ERROR: "gam csv " should be followed by a full GAM command...' sys.exit(3) argv_template = sys.argv[4:] + substring_replacements = re.findall(r'~~(.*?)~~', u' '.join(argv_template)) items = list() for row in input_file: argv = list() for arg in argv_template: + for substring_replacement in substring_replacements: + try: + arg = arg.replace(u'~~%s~~' % substring_replacement, row[substring_replacement]) + except KeyError: + print u'%s is not in %s' % (substring_replacement, row) + sys.exit(3) if arg[0] != '~': argv.append(arg) elif arg[1:] in row: @@ -8683,6 +8803,8 @@ try: doCreateDomain() elif sys.argv[2].lower() in [u'domainalias', u'aliasdomain']: doCreateDomainAlias() + elif sys.argv[2].lower() in [u'admin']: + doCreateAdmin() else: print u'ERROR: %s is not a valid argument for "gam create"' % sys.argv[2] sys.exit(2) @@ -8786,6 +8908,8 @@ try: doDelDomain() elif sys.argv[2].lower() in [u'domainalias',]: doDelDomainAlias() + elif sys.argv[2].lower() in [u'admin',]: + doDelAdmin() else: print u'ERROR: %s is not a valid argument for "gam delete"' % sys.argv[2] sys.exit(2) @@ -8877,6 +9001,8 @@ try: doPrintTransferApps() elif sys.argv[2].lower() in [u'domains']: doPrintDomains() + elif sys.argv[2].lower() in [u'admins']: + doPrintAdmins() else: print u'ERROR: %s is not a valid argument for "gam print"' % sys.argv[2] sys.exit(2) diff --git a/src/gdata/apps/res_cal/__init__.py b/src/gdata/apps/res_cal/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/src/gdata/apps/res_cal/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/gdata/apps/res_cal/service.py b/src/gdata/apps/res_cal/service.py deleted file mode 100644 index bb173bbc..00000000 --- a/src/gdata/apps/res_cal/service.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (C) 2008 Google, Inc. -# -# 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. - -"""Allow Google Apps domain administrators to create/modify/delete resource calendars. - - ResCalService: Interact with Resource Calendars.""" - -__author__ = 'jlee@pbu.edu' - -import gdata.apps -import gdata.apps.service -import gdata.service - -class ResCalService(gdata.apps.service.PropertyService): - """Client for the Google Apps Resource Calendar service.""" - - def _serviceUrl(self, domain=None): - if domain is None: - domain = self.domain - return '/a/feeds/calendar/resource/2.0/%s' % domain - - def CreateResourceCalendar(self, id, common_name, description=None, type=None): - - uri = self._serviceUrl() - properties = {} - properties['resourceId'] = id - properties['resourceCommonName'] = common_name - if description != None: - properties['resourceDescription'] = description - if type != None: - properties['resourceType'] = type - return self._PostProperties(uri, properties) - - def RetrieveResourceCalendar(self, id): - - uri = self._serviceUrl()+'/'+id - return self._GetProperties(uri) - - def RetrieveAllResourceCalendars(self): - - uri = self._serviceUrl()+'/' - return self._GetPropertiesList(uri) - - def UpdateResourceCalendar(self, id, common_name=None, description=None, type=None): - - uri = self._serviceUrl()+'/'+id - properties = {} - properties['resourceId'] = id - if common_name != None: - properties['resourceCommonName'] = common_name - if description != None: - properties['resourceDescription'] = description - if type != None: - properties['resourceType'] = type - return self._PutProperties(uri, properties) - - def DeleteResourceCalendar(self, id): - - uri = self._serviceUrl()+'/'+id - return self._DeleteProperties(uri) \ No newline at end of file