def modifySettings(): calendarId, cal = buildCalendarDataGAPIObject(sys.argv[2]) if not cal: return body = {} i = 4 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'description': body['description'] = sys.argv[i+1] i += 2 elif myarg == 'location': body['location'] = sys.argv[i+1] i += 2 elif myarg == 'summary': body['summary'] = sys.argv[i+1] i += 2 elif myarg == 'timezone': body['timeZone'] = sys.argv[i+1] i += 2 else: controlflow.invalid_argument_exit( sys.argv[i], "gam calendar <email> modify") gapi.call(cal.calendars(), 'patch', calendarId=calendarId, body=body)
def infoCalendar(users): calendarId = normalizeCalendarId(sys.argv[5], checkPrimary=True) i = 0 count = len(users) for user in users: i += 1 user, cal = buildCalendarGAPIObject(user) if not cal: continue result = gapi.call(cal.calendarList(), 'get', soft_errors=True, calendarId=calendarId) if result: print(f'User: {user}, Calendar:{display.current_count(i, count)}') _showCalendar(result, 1, 1)
def test_call_retries_with_soft_errors(self, mock_error_detail): mock_error_detail.return_value = (-1, 'aReason', 'some message') # Make the request fail first, then return the proper response on the retry. fake_http_error = create_http_error(403, 'aReason', 'unused message') fake_200_response = MagicMock() self.mock_method.return_value.execute.side_effect = [ fake_http_error, fake_200_response ] response = gapi.call( self.mock_service, self.mock_method_name, soft_errors=True) self.assertEqual(response, fake_200_response) self.assertEqual( self.mock_service._http.request.credentials.refresh.call_count, 1) self.assertEqual(self.mock_method.return_value.execute.call_count, 2)
def test_call_retries_requests_with_backoff_on_servernotfounderror( self, mock_wait_on_failure): fake_servernotfounderror = gapi.httplib2.ServerNotFoundError() fake_200_response = MagicMock() # Fail once, then succeed on retry self.mock_method.return_value.execute.side_effect = [ fake_servernotfounderror, fake_200_response ] http_connections = self.mock_service._http.connections response = gapi.call(self.mock_service, self.mock_method_name) self.assertEqual(response, fake_200_response) # HTTP cached connections should be cleared on receiving this error self.assertNotEqual(http_connections, self.mock_service._http.connections) self.assertEqual(self.mock_method.return_value.execute.call_count, 2) # Make sure a backoff technique was used for retry. self.assertEqual(mock_wait_on_failure.call_count, 1)
def test_call_retries_request_for_default_retry_reasons( self, mock_wait_on_failure): # Test using one of the default retry reasons default_throw_reason = errors.ErrorReason.BACKEND_ERROR self.assertIn(default_throw_reason, errors.DEFAULT_RETRY_REASONS) fake_http_error = create_http_error(404, default_throw_reason, 'message') fake_200_response = MagicMock() # Fail once, then succeed on retry self.mock_method.return_value.execute.side_effect = [ fake_http_error, fake_200_response ] response = gapi.call( self.mock_service, self.mock_method_name, retry_reasons=[]) self.assertEqual(response, fake_200_response) self.assertEqual(self.mock_method.return_value.execute.call_count, 2) # Make sure a backoff technique was used for retry. self.assertEqual(mock_wait_on_failure.call_count, 1)
def test_call_returns_basic_200_response(self): response = gapi.call(self.mock_service, self.mock_method_name) self.assertEqual(response, self.mock_method().execute.return_value)
def test_call_exits_on_request_valueerror(self): self.mock_method.return_value.execute.side_effect = ValueError() with self.assertRaises(SystemExit): gapi.call(self.mock_service, self.mock_method_name)
def doUpdateCros(): cd = gapi.directory.buildGAPIObject() i, devices = getCrOSDeviceEntity(3, cd) update_body = {} action_body = {} orgUnitPath = None ack_wipe = False while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'user': update_body['annotatedUser'] = sys.argv[i+1] i += 2 elif myarg == 'location': update_body['annotatedLocation'] = sys.argv[i+1] i += 2 elif myarg == 'notes': update_body['notes'] = sys.argv[i+1].replace('\\n', '\n') i += 2 elif myarg in ['tag', 'asset', 'assetid']: update_body['annotatedAssetId'] = sys.argv[i+1] i += 2 elif myarg in ['ou', 'org']: orgUnitPath = __main__.getOrgUnitItem(sys.argv[i+1]) i += 2 elif myarg == 'action': action = sys.argv[i+1].lower().replace('_', '').replace('-', '') deprovisionReason = None if action in ['deprovisionsamemodelreplace', 'deprovisionsamemodelreplacement']: action = 'deprovision' deprovisionReason = 'same_model_replacement' elif action in ['deprovisiondifferentmodelreplace', 'deprovisiondifferentmodelreplacement']: action = 'deprovision' deprovisionReason = 'different_model_replacement' elif action in ['deprovisionretiringdevice']: action = 'deprovision' deprovisionReason = 'retiring_device' elif action not in ['disable', 'reenable']: controlflow.system_error_exit(2, f'expected action of ' \ f'deprovision_same_model_replace, ' \ f'deprovision_different_model_replace, ' \ f'deprovision_retiring_device, disable or reenable,' f' got {action}') action_body = {'action': action} if deprovisionReason: action_body['deprovisionReason'] = deprovisionReason i += 2 elif myarg == 'acknowledgedevicetouchrequirement': ack_wipe = True i += 1 else: controlflow.invalid_argument_exit(sys.argv[i], "gam update cros") i = 0 count = len(devices) if action_body: if action_body['action'] == 'deprovision' and not ack_wipe: print(f'WARNING: Refusing to deprovision {count} devices because ' 'acknowledge_device_touch_requirement not specified. ' \ 'Deprovisioning a device means the device will have to ' \ 'be physically wiped and re-enrolled to be managed by ' \ 'your domain again. This requires physical access to ' \ 'the device and is very time consuming to perform for ' \ 'each device. Please add ' \ '"acknowledge_device_touch_requirement" to the GAM ' \ 'command if you understand this and wish to proceed ' \ 'with the deprovision. Please also be aware that ' \ 'deprovisioning can have an effect on your device ' \ 'license count. See ' \ 'https://support.google.com/chrome/a/answer/3523633 '\ 'for full details.') sys.exit(3) for deviceId in devices: i += 1 cur_count = __main__.currentCount(i, count) print(f' performing action {action} for {deviceId}{cur_count}') gapi.call(cd.chromeosdevices(), function='action', customerId=GC_Values[GC_CUSTOMER_ID], resourceId=deviceId, body=action_body) else: if update_body: for deviceId in devices: i += 1 current_count = __main__.currentCount(i, count) print(f' updating {deviceId}{current_count}') gapi.call(cd.chromeosdevices(), 'update', customerId=GC_Values[GC_CUSTOMER_ID], deviceId=deviceId, body=update_body) if orgUnitPath: # split moves into max 50 devices per batch for l in range(0, len(devices), 50): move_body = {'deviceIds': devices[l:l+50]} print(f' moving {len(move_body["deviceIds"])} devices to ' \ f'{orgUnitPath}') gapi.call(cd.chromeosdevices(), 'moveDevicesToOu', customerId=GC_Values[GC_CUSTOMER_ID], orgUnitPath=orgUnitPath, body=move_body)
def updateMatter(action=None): v = buildGAPIObject() matterId = getMatterItem(v, sys.argv[3]) body = {} action_kwargs = {'body': {}} add_collaborators = [] remove_collaborators = [] cd = None i = 4 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'action': action = sys.argv[i + 1].lower() if action not in VAULT_MATTER_ACTIONS: controlflow.system_error_exit(3, f'allowed actions are ' \ f'{", ".join(VAULT_MATTER_ACTIONS)}, got {action}') i += 2 elif myarg == 'name': body['name'] = sys.argv[i + 1] i += 2 elif myarg == 'description': body['description'] = sys.argv[i + 1] i += 2 elif myarg in ['addcollaborator', 'addcollaborators']: if not cd: cd = __main__.buildGAPIObject('directory') add_collaborators.extend(validateCollaborators( sys.argv[i + 1], cd)) i += 2 elif myarg in ['removecollaborator', 'removecollaborators']: if not cd: cd = __main__.buildGAPIObject('directory') remove_collaborators.extend( validateCollaborators(sys.argv[i + 1], cd)) i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], "gam update matter") if action == 'delete': action_kwargs = {} if body: print(f'Updating matter {sys.argv[3]}...') if 'name' not in body or 'description' not in body: # bah, API requires name/description to be sent # on update even when it's not changing result = gapi.call(v.matters(), 'get', matterId=matterId, view='BASIC') body.setdefault('name', result['name']) body.setdefault('description', result.get('description')) gapi.call(v.matters(), 'update', body=body, matterId=matterId) if action: print(f'Performing {action} on matter {sys.argv[3]}') gapi.call(v.matters(), action, matterId=matterId, **action_kwargs) for collaborator in add_collaborators: print(f' adding collaborator {collaborator["email"]}') body = { 'matterPermission': { 'role': 'COLLABORATOR', 'accountId': collaborator['id'] } } gapi.call(v.matters(), 'addPermissions', matterId=matterId, body=body) for collaborator in remove_collaborators: print(f' removing collaborator {collaborator["email"]}') gapi.call(v.matters(), 'removePermissions', matterId=matterId, body={'accountId': collaborator['id']})
def changeAttendees(users): do_it = True i = 5 allevents = False start_date = end_date = None while len(sys.argv) > i: myarg = sys.argv[i].lower() if myarg == 'csv': csv_file = sys.argv[i + 1] i += 2 elif myarg == 'dryrun': do_it = False i += 1 elif myarg == 'start': start_date = utils.get_time_or_delta_from_now(sys.argv[i + 1]) i += 2 elif myarg == 'end': end_date = utils.get_time_or_delta_from_now(sys.argv[i + 1]) i += 2 elif myarg == 'allevents': allevents = True i += 1 else: controlflow.invalid_argument_exit( sys.argv[i], "gam <users> update calattendees") attendee_map = {} f = fileutils.open_file(csv_file) csvFile = csv.reader(f) for row in csvFile: attendee_map[row[0].lower()] = row[1].lower() fileutils.close_file(f) for user in users: sys.stdout.write(f'Checking user {user}\n') user, cal = buildCalendarGAPIObject(user) if not cal: continue page_token = None while True: events_page = gapi.call(cal.events(), 'list', calendarId=user, pageToken=page_token, timeMin=start_date, timeMax=end_date, showDeleted=False, showHiddenInvitations=False) print(f'Got {len(events_page.get("items", []))}') for event in events_page.get('items', []): if event['status'] == 'cancelled': # print u' skipping cancelled event' continue try: event_summary = event['summary'] except (KeyError, UnicodeEncodeError, UnicodeDecodeError): event_summary = event['id'] try: organizer = event['organizer']['email'].lower() if not allevents and organizer != user: #print(f' skipping not-my-event {event_summary}') continue except KeyError: pass # no email for organizer needs_update = False try: for attendee in event['attendees']: try: if attendee['email'].lower() in attendee_map: old_email = attendee['email'].lower() new_email = attendee_map[ attendee['email'].lower()] print(f' SWITCHING attendee {old_email} to ' \ f'{new_email} for {event_summary}') event['attendees'].remove(attendee) event['attendees'].append({'email': new_email}) needs_update = True except KeyError: # no email for that attendee pass except KeyError: continue # no attendees if needs_update: body = {} body['attendees'] = event['attendees'] print(f'UPDATING {event_summary}') if do_it: gapi.call(cal.events(), 'patch', calendarId=user, eventId=event['id'], sendNotifications=False, body=body) else: print(' not pulling the trigger.') # else: # print(f' no update needed for {event_summary}') try: page_token = events_page['nextPageToken'] except KeyError: break
def createExport(): v = buildGAPIObject() allowed_corpuses = gapi.get_enum_values_minus_unspecified( v._rootDesc['schemas']['Query']['properties']['corpus']['enum']) allowed_scopes = gapi.get_enum_values_minus_unspecified( v._rootDesc['schemas']['Query']['properties']['dataScope']['enum']) allowed_formats = gapi.get_enum_values_minus_unspecified( v._rootDesc['schemas']['MailExportOptions']['properties'] ['exportFormat']['enum']) export_format = 'MBOX' showConfidentialModeContent = None # default to not even set matterId = None body = {'query': {'dataScope': 'ALL_DATA'}, 'exportOptions': {}} i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'matter': matterId = getMatterItem(v, sys.argv[i+1]) body['matterId'] = matterId i += 2 elif myarg == 'name': body['name'] = sys.argv[i+1] i += 2 elif myarg == 'corpus': body['query']['corpus'] = sys.argv[i+1].upper() if body['query']['corpus'] not in allowed_corpuses: controlflow.expected_argument_exit( "corpus", ", ".join(allowed_corpuses), sys.argv[i+1]) i += 2 elif myarg in VAULT_SEARCH_METHODS_MAP: if body['query'].get('searchMethod'): message = f'Multiple search methods ' \ f'({", ".join(VAULT_SEARCH_METHODS_LIST)})' \ f'specified, only one is allowed' controlflow.system_error_exit(3, message) searchMethod = VAULT_SEARCH_METHODS_MAP[myarg] body['query']['searchMethod'] = searchMethod if searchMethod == 'ACCOUNT': body['query']['accountInfo'] = { 'emails': sys.argv[i+1].split(',')} i += 2 elif searchMethod == 'ORG_UNIT': body['query']['orgUnitInfo'] = { 'orgUnitId': __main__.getOrgUnitId(sys.argv[i+1])[1]} i += 2 elif searchMethod == 'SHARED_DRIVE': body['query']['sharedDriveInfo'] = { 'sharedDriveIds': sys.argv[i+1].split(',')} i += 2 elif searchMethod == 'ROOM': body['query']['hangoutsChatInfo'] = { 'roomId': sys.argv[i+1].split(',')} i += 2 else: i += 1 elif myarg == 'scope': body['query']['dataScope'] = sys.argv[i+1].upper() if body['query']['dataScope'] not in allowed_scopes: controlflow.expected_argument_exit( "scope", ", ".join(allowed_scopes), sys.argv[i+1]) i += 2 elif myarg in ['terms']: body['query']['terms'] = sys.argv[i+1] i += 2 elif myarg in ['start', 'starttime']: body['query']['startTime'] = utils.get_date_zero_time_or_full_time( sys.argv[i+1]) i += 2 elif myarg in ['end', 'endtime']: body['query']['endTime'] = utils.get_date_zero_time_or_full_time( sys.argv[i+1]) i += 2 elif myarg in ['timezone']: body['query']['timeZone'] = sys.argv[i+1] i += 2 elif myarg in ['excludedrafts']: body['query']['mailOptions'] = { 'excludeDrafts': __main__.getBoolean(sys.argv[i+1], myarg)} i += 2 elif myarg in ['driveversiondate']: body['query'].setdefault('driveOptions', {})['versionDate'] = \ utils.get_date_zero_time_or_full_time(sys.argv[i+1]) i += 2 elif myarg in ['includeshareddrives', 'includeteamdrives']: body['query'].setdefault('driveOptions', {})[ 'includeSharedDrives'] = __main__.getBoolean(sys.argv[i+1], myarg) i += 2 elif myarg in ['includerooms']: body['query']['hangoutsChatOptions'] = { 'includeRooms': __main__.getBoolean(sys.argv[i+1], myarg)} i += 2 elif myarg in ['format']: export_format = sys.argv[i+1].upper() if export_format not in allowed_formats: controlflow.expected_argument_exit( "export format", ", ".join(allowed_formats), export_format) i += 2 elif myarg in ['showconfidentialmodecontent']: showConfidentialModeContent = __main__.getBoolean(sys.argv[i+1], myarg) i += 2 elif myarg in ['region']: allowed_regions = gapi.get_enum_values_minus_unspecified( v._rootDesc['schemas']['ExportOptions']['properties'][ 'region']['enum']) body['exportOptions']['region'] = sys.argv[i+1].upper() if body['exportOptions']['region'] not in allowed_regions: controlflow.expected_argument_exit("region", ", ".join( allowed_regions), body['exportOptions']['region']) i += 2 elif myarg in ['includeaccessinfo']: body['exportOptions'].setdefault('driveOptions', {})[ 'includeAccessInfo'] = __main__.getBoolean(sys.argv[i+1], myarg) i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], "gam create export") if not matterId: controlflow.system_error_exit( 3, 'you must specify a matter for the new export.') if 'corpus' not in body['query']: controlflow.system_error_exit(3, f'you must specify a corpus for the ' \ f'new export. Choose one of {", ".join(allowed_corpuses)}') if 'searchMethod' not in body['query']: controlflow.system_error_exit(3, f'you must specify a search method ' \ 'for the new export. Choose one of ' \ f'{", ".join(VAULT_SEARCH_METHODS_LIST)}') if 'name' not in body: corpus_name = body["query"]["corpus"] corpus_date = datetime.datetime.now() body['name'] = f'GAM {corpus_name} export - {corpus_date}' options_field = None if body['query']['corpus'] == 'MAIL': options_field = 'mailOptions' elif body['query']['corpus'] == 'GROUPS': options_field = 'groupsOptions' elif body['query']['corpus'] == 'HANGOUTS_CHAT': options_field = 'hangoutsChatOptions' if options_field: body['exportOptions'].pop('driveOptions', None) body['exportOptions'][options_field] = {'exportFormat': export_format} if showConfidentialModeContent is not None: body['exportOptions'][options_field][ 'showConfidentialModeContent'] = showConfidentialModeContent results = gapi.call(v.matters().exports(), 'create', matterId=matterId, body=body) print(f'Created export {results["id"]}') display.print_json(results)
def updateHold(): v = buildGAPIObject() hold = sys.argv[3] matterId = None body = {} query = None add_accounts = [] del_accounts = [] start_time = None end_time = None i = 4 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'matter': matterId = getMatterItem(v, sys.argv[i+1]) holdId = convertHoldNameToID(v, hold, matterId) i += 2 elif myarg == 'query': query = sys.argv[i+1] i += 2 elif myarg in ['orgunit', 'ou']: body['orgUnit'] = {'orgUnitId': __main__.getOrgUnitId(sys.argv[i+1])[1]} i += 2 elif myarg in ['start', 'starttime']: start_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1]) i += 2 elif myarg in ['end', 'endtime']: end_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1]) i += 2 elif myarg in ['addusers', 'addaccounts', 'addgroups']: add_accounts = sys.argv[i+1].split(',') i += 2 elif myarg in ['removeusers', 'removeaccounts', 'removegroups']: del_accounts = sys.argv[i+1].split(',') i += 2 else: controlflow.invalid_argument_exit(myarg, "gam update hold") if not matterId: controlflow.system_error_exit( 3, 'you must specify a matter for the hold.') if query or start_time or end_time or body.get('orgUnit'): fields = 'corpus,query,orgUnit' old_body = gapi.call(v.matters().holds( ), 'get', matterId=matterId, holdId=holdId, fields=fields) body['query'] = old_body['query'] body['corpus'] = old_body['corpus'] if 'orgUnit' in old_body and 'orgUnit' not in body: # bah, API requires this to be sent # on update even when it's not changing body['orgUnit'] = old_body['orgUnit'] query_type = f'{body["corpus"].lower()}Query' if body['corpus'] == 'DRIVE': if query: try: body['query'][query_type] = json.loads(query) except ValueError as e: message = f'{str(e)}, query: {query}' controlflow.system_error_exit(3, message) elif body['corpus'] in ['GROUPS', 'MAIL']: if query: body['query'][query_type]['terms'] = query if start_time: body['query'][query_type]['startTime'] = start_time if end_time: body['query'][query_type]['endTime'] = end_time if body: print(f'Updating hold {hold} / {holdId}') gapi.call(v.matters().holds(), 'update', matterId=matterId, holdId=holdId, body=body) if add_accounts or del_accounts: cd = __main__.buildGAPIObject('directory') for account in add_accounts: print(f'adding {account} to hold.') add_body = {'accountId': __main__.convertEmailAddressToUID(account, cd)} gapi.call(v.matters().holds().accounts(), 'create', matterId=matterId, holdId=holdId, body=add_body) for account in del_accounts: print(f'removing {account} from hold.') accountId = __main__.convertEmailAddressToUID(account, cd) gapi.call(v.matters().holds().accounts(), 'delete', matterId=matterId, holdId=holdId, accountId=accountId)
def createHold(): v = buildGAPIObject() allowed_corpuses = gapi.get_enum_values_minus_unspecified( v._rootDesc['schemas']['Hold']['properties']['corpus']['enum']) body = {'query': {}} i = 3 query = None start_time = None end_time = None matterId = None accounts = [] while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'name': body['name'] = sys.argv[i+1] i += 2 elif myarg == 'query': query = sys.argv[i+1] i += 2 elif myarg == 'corpus': body['corpus'] = sys.argv[i+1].upper() if body['corpus'] not in allowed_corpuses: controlflow.expected_argument_exit( "corpus", ", ".join(allowed_corpuses), sys.argv[i+1]) i += 2 elif myarg in ['accounts', 'users', 'groups']: accounts = sys.argv[i+1].split(',') i += 2 elif myarg in ['orgunit', 'ou']: body['orgUnit'] = { 'orgUnitId': __main__.getOrgUnitId(sys.argv[i+1])[1]} i += 2 elif myarg in ['start', 'starttime']: start_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1]) i += 2 elif myarg in ['end', 'endtime']: end_time = utils.get_date_zero_time_or_full_time(sys.argv[i+1]) i += 2 elif myarg == 'matter': matterId = getMatterItem(v, sys.argv[i+1]) i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], "gam create hold") if not matterId: controlflow.system_error_exit( 3, 'you must specify a matter for the new hold.') if not body.get('name'): controlflow.system_error_exit( 3, 'you must specify a name for the new hold.') if not body.get('corpus'): controlflow.system_error_exit(3, f'you must specify a corpus for ' \ f'the new hold. Choose one of {", ".join(allowed_corpuses)}') if body['corpus'] == 'HANGOUTS_CHAT': query_type = 'hangoutsChatQuery' else: query_type = f'{body["corpus"].lower()}Query' body['query'][query_type] = {} if body['corpus'] == 'DRIVE': if query: try: body['query'][query_type] = json.loads(query) except ValueError as e: controlflow.system_error_exit(3, f'{str(e)}, query: {query}') elif body['corpus'] in ['GROUPS', 'MAIL']: if query: body['query'][query_type] = {'terms': query} if start_time: body['query'][query_type]['startTime'] = start_time if end_time: body['query'][query_type]['endTime'] = end_time if accounts: body['accounts'] = [] cd = __main__.buildGAPIObject('directory') account_type = 'group' if body['corpus'] == 'GROUPS' else 'user' for account in accounts: body['accounts'].append( {'accountId': __main__.convertEmailAddressToUID(account, cd, account_type)} ) holdId = gapi.call(v.matters().holds(), 'create', matterId=matterId, body=body, fields='holdId') print(f'Created hold {holdId["holdId"]}')
def showUsageParameters(): rep = buildGAPIObject() throw_reasons = [ gapi.errors.ErrorReason.INVALID, gapi.errors.ErrorReason.BAD_REQUEST ] todrive = False if len(sys.argv) == 3: controlflow.missing_argument_exit('user or customer', 'report usageparameters') report = sys.argv[3].lower() titles = ['parameter'] if report == 'customer': endpoint = rep.customerUsageReports() kwargs = {} elif report == 'user': endpoint = rep.userUsageReport() kwargs = {'userKey': __main__._getValueFromOAuth('email')} else: controlflow.expected_argument_exit('usageparameters', ['user', 'customer'], report) customerId = GC_Values[GC_CUSTOMER_ID] if customerId == MY_CUSTOMER: customerId = None tryDate = datetime.date.today().strftime(YYYYMMDD_FORMAT) partial_apps = [] all_parameters = [] one_day = datetime.timedelta(days=1) i = 4 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'todrive': todrive = True i += 1 else: controlflow.invalid_argument_exit(sys.argv[i], "gam report usageparameters") while True: try: response = gapi.call(endpoint, 'get', throw_reasons=throw_reasons, date=tryDate, customerId=customerId, **kwargs) partial_on_thisday = [] for warning in response.get('warnings', []): for data in warning.get('data', []): if data.get('key') == 'application': partial_on_thisday.append(data['value']) if partial_apps: partial_apps = [ app for app in partial_apps if app in partial_on_thisday ] else: partial_apps = partial_on_thisday for parameter in response['usageReports'][0]['parameters']: name = parameter.get('name') if name and name not in all_parameters: all_parameters.append(name) if not partial_apps: break tryDate = (utils.get_yyyymmdd(tryDate, returnDateTime=True) - \ one_day).strftime(YYYYMMDD_FORMAT) except gapi.errors.GapiInvalidError as e: tryDate = _adjust_date(str(e)) all_parameters.sort() csvRows = [] for parameter in all_parameters: csvRows.append({'parameter': parameter}) display.write_csv_file(csvRows, titles, f'{report.capitalize()} Report Usage Parameters', todrive)
def wipeData(): calendarId, cal = buildCalendarDataGAPIObject(sys.argv[2]) if not cal: return gapi.call(cal.calendars(), 'clear', calendarId=calendarId)
def getEventAttributes(i, calendarId, cal, body, action): # Default to external only so non-Google # calendars are notified of changes sendUpdates = 'externalOnly' action = 'update' if body else 'add' while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg in ['notifyattendees', 'sendnotifications', 'sendupdates']: sendUpdates, i = getSendUpdates(myarg, i, cal) elif myarg == 'attendee': body.setdefault('attendees', []) body['attendees'].append({'email': sys.argv[i + 1]}) i += 2 elif myarg == 'removeattendee' and action == 'update': remove_email = sys.argv[i + 1].lower() if 'attendees' in body: body['attendees'] = _remove_attendee(body['attendees'], remove_email) i += 2 elif myarg == 'optionalattendee': body.setdefault('attendees', []) body['attendees'].append({ 'email': sys.argv[i + 1], 'optional': True }) i += 2 elif myarg == 'anyonecanaddself': body['anyoneCanAddSelf'] = True i += 1 elif myarg == 'description': body['description'] = sys.argv[i + 1].replace('\\n', '\n') i += 2 elif myarg == 'replacedescription' and action == 'update': search = sys.argv[i + 1] replace = sys.argv[i + 2] if 'description' in body: body['description'] = re.sub(search, replace, body['description']) i += 3 elif myarg == 'start': if sys.argv[i + 1].lower() == 'allday': body['start'] = {'date': utils.get_yyyymmdd(sys.argv[i + 2])} i += 3 else: start_time = utils.get_time_or_delta_from_now(sys.argv[i + 1]) body['start'] = {'dateTime': start_time} i += 2 elif myarg == 'end': if sys.argv[i + 1].lower() == 'allday': body['end'] = {'date': utils.get_yyyymmdd(sys.argv[i + 2])} i += 3 else: end_time = utils.get_time_or_delta_from_now(sys.argv[i + 1]) body['end'] = {'dateTime': end_time} i += 2 elif myarg == 'guestscantinviteothers': body['guestsCanInviteOthers'] = False i += 1 elif myarg == 'guestscaninviteothers': body['guestsCanInviteTohters'] = __main__.getBoolean( sys.argv[i + 1], 'guestscaninviteothers') i += 2 elif myarg == 'guestscantseeothers': body['guestsCanSeeOtherGuests'] = False i += 1 elif myarg == 'guestscanseeothers': body['guestsCanSeeOtherGuests'] = __main__.getBoolean( sys.argv[i + 1], 'guestscanseeothers') i += 2 elif myarg == 'guestscanmodify': body['guestsCanModify'] = __main__.getBoolean( sys.argv[i + 1], 'guestscanmodify') i += 2 elif myarg == 'id': if action == 'update': controlflow.invalid_argument_exit( 'id', 'gam calendar <calendar> updateevent') body['id'] = sys.argv[i + 1] i += 2 elif myarg == 'summary': body['summary'] = sys.argv[i + 1] i += 2 elif myarg == 'location': body['location'] = sys.argv[i + 1] i += 2 elif myarg == 'available': body['transparency'] = 'transparent' i += 1 elif myarg == 'transparency': validTransparency = ['opaque', 'transparent'] if sys.argv[i + 1].lower() in validTransparency: body['transparency'] = sys.argv[i + 1].lower() else: controlflow.expected_argument_exit( 'transparency', ", ".join(validTransparency), sys.argv[i + 1]) i += 2 elif myarg == 'visibility': validVisibility = ['default', 'public', 'private'] if sys.argv[i + 1].lower() in validVisibility: body['visibility'] = sys.argv[i + 1].lower() else: controlflow.expected_argument_exit("visibility", ", ".join(validVisibility), sys.argv[i + 1]) i += 2 elif myarg == 'tentative': body['status'] = 'tentative' i += 1 elif myarg == 'status': validStatus = ['confirmed', 'tentative', 'cancelled'] if sys.argv[i + 1].lower() in validStatus: body['status'] = sys.argv[i + 1].lower() else: controlflow.expected_argument_exit('visibility', ', '.join(validStatus), sys.argv[i + 1]) i += 2 elif myarg == 'source': body['source'] = {'title': sys.argv[i + 1], 'url': sys.argv[i + 2]} i += 3 elif myarg == 'noreminders': body['reminders'] = {'useDefault': False} i += 1 elif myarg == 'reminder': minutes = \ __main__.getInteger(sys.argv[i+1], myarg, minVal=0, maxVal=CALENDAR_REMINDER_MAX_MINUTES) reminder = {'minutes': minutes, 'method': sys.argv[i + 2]} body.setdefault('reminders', { 'overrides': [], 'useDefault': False }) body['reminders']['overrides'].append(reminder) i += 3 elif myarg == 'recurrence': body.setdefault('recurrence', []) body['recurrence'].append(sys.argv[i + 1]) i += 2 elif myarg == 'timezone': timeZone = sys.argv[i + 1] i += 2 elif myarg == 'privateproperty': if 'extendedProperties' not in body: body['extendedProperties'] = {'private': {}, 'shared': {}} body['extendedProperties']['private'][sys.argv[i + 1]] = sys.argv[i + 2] i += 3 elif myarg == 'sharedproperty': if 'extendedProperties' not in body: body['extendedProperties'] = {'private': {}, 'shared': {}} body['extendedProperties']['shared'][sys.argv[i + 1]] = sys.argv[i + 2] i += 3 elif myarg == 'colorindex': body['colorId'] = __main__.getInteger( sys.argv[i + 1], myarg, CALENDAR_EVENT_MIN_COLOR_INDEX, CALENDAR_EVENT_MAX_COLOR_INDEX) i += 2 elif myarg == 'hangoutsmeet': body['conferenceData'] = { 'createRequest': { 'requestId': f'{str(uuid.uuid4())}' } } i += 1 else: controlflow.invalid_argument_exit( sys.argv[i], f'gam calendar <email> {action}event') if ('recurrence' in body) and (('start' in body) or ('end' in body)): if not timeZone: timeZone = gapi.call(cal.calendars(), 'get', calendarId=calendarId, fields='timeZone')['timeZone'] if 'start' in body: body['start']['timeZone'] = timeZone if 'end' in body: body['end']['timeZone'] = timeZone return (sendUpdates, body)
def doGetCrosInfo(): cd = gapi.directory.buildGAPIObject() i, devices = getCrOSDeviceEntity(3, cd) downloadfile = None targetFolder = GC_Values[GC_DRIVE_DIR] projection = None fieldsList = [] noLists = False startDate = endDate = None listLimit = 0 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'nolists': noLists = True i += 1 elif myarg == 'listlimit': listLimit = __main__.getInteger(sys.argv[i+1], myarg, minVal=-1) i += 2 elif myarg in CROS_START_ARGUMENTS: startDate = _getFilterDate(sys.argv[i+1]) i += 2 elif myarg in CROS_END_ARGUMENTS: endDate = _getFilterDate(sys.argv[i+1]) i += 2 elif myarg == 'allfields': projection = 'FULL' fieldsList = [] i += 1 elif myarg in PROJECTION_CHOICES_MAP: projection = PROJECTION_CHOICES_MAP[myarg] if projection == 'FULL': fieldsList = [] else: fieldsList = CROS_BASIC_FIELDS_LIST[:] i += 1 elif myarg in CROS_ARGUMENT_TO_PROPERTY_MAP: fieldsList.extend(CROS_ARGUMENT_TO_PROPERTY_MAP[myarg]) i += 1 elif myarg == 'fields': fieldNameList = sys.argv[i+1] for field in fieldNameList.lower().replace(',', ' ').split(): if field in CROS_ARGUMENT_TO_PROPERTY_MAP: fieldsList.extend(CROS_ARGUMENT_TO_PROPERTY_MAP[field]) if field in CROS_ACTIVE_TIME_RANGES_ARGUMENTS + \ CROS_DEVICE_FILES_ARGUMENTS + \ CROS_RECENT_USERS_ARGUMENTS: projection = 'FULL' noLists = False else: controlflow.invalid_argument_exit( field, "gam info cros fields") i += 2 elif myarg == 'downloadfile': downloadfile = sys.argv[i+1] if downloadfile.lower() == 'latest': downloadfile = downloadfile.lower() i += 2 elif myarg == 'targetfolder': targetFolder = os.path.expanduser(sys.argv[i+1]) if not os.path.isdir(targetFolder): os.makedirs(targetFolder) i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], "gam info cros") if fieldsList: fieldsList.append('deviceId') fields = ','.join(set(fieldsList)).replace('.', '/') else: fields = None i = 0 device_count = len(devices) for deviceId in devices: i += 1 cros = gapi.call(cd.chromeosdevices(), 'get', customerId=GC_Values[GC_CUSTOMER_ID], deviceId=deviceId, projection=projection, fields=fields) print(f'CrOS Device: {deviceId} ({i} of {device_count})') if 'notes' in cros: cros['notes'] = cros['notes'].replace('\n', '\\n') if 'autoUpdateExpiration' in cros: cros['autoUpdateExpiration'] = utils.formatTimestampYMD( cros['autoUpdateExpiration']) _checkTPMVulnerability(cros) for up in CROS_SCALAR_PROPERTY_PRINT_ORDER: if up in cros: if isinstance(cros[up], str): print(f' {up}: {cros[up]}') else: sys.stdout.write(f' {up}:') display.print_json(cros[up], ' ') if not noLists: activeTimeRanges = _filterTimeRanges( cros.get('activeTimeRanges', []), startDate, endDate) lenATR = len(activeTimeRanges) if lenATR: print(' activeTimeRanges') num_ranges = min(lenATR, listLimit or lenATR) for activeTimeRange in activeTimeRanges[:num_ranges]: active_date = activeTimeRange["date"] active_time = activeTimeRange["activeTime"] duration = utils.formatMilliSeconds(active_time) minutes = active_time // 60000 print(f' date: {active_date}') print(f' activeTime: {active_time}') print(f' duration: {duration}') print(f' minutes: {minutes}') recentUsers = cros.get('recentUsers', []) lenRU = len(recentUsers) if lenRU: print(' recentUsers') num_ranges = min(lenRU, listLimit or lenRU) for recentUser in recentUsers[:num_ranges]: useremail = recentUser.get("email") if not useremail: if recentUser["type"] == "USER_TYPE_UNMANAGED": useremail = 'UnmanagedUser' else: useremail = 'Unknown' print(f' type: {recentUser["type"]}') print(f' email: {useremail}') deviceFiles = _filterCreateReportTime( cros.get('deviceFiles', []), 'createTime', startDate, endDate) lenDF = len(deviceFiles) if lenDF: num_ranges = min(lenDF, listLimit or lenDF) print(' deviceFiles') for deviceFile in deviceFiles[:num_ranges]: device_type = deviceFile['type'] create_time = deviceFile['createTime'] print(f' {device_type}: {create_time}') if downloadfile: deviceFiles = cros.get('deviceFiles', []) lenDF = len(deviceFiles) if lenDF: if downloadfile == 'latest': deviceFile = deviceFiles[-1] else: for deviceFile in deviceFiles: if deviceFile['createTime'] == downloadfile: break else: print(f'ERROR: file {downloadfile} not ' \ f'available to download.') deviceFile = None if deviceFile: created = deviceFile["createTime"] downloadfile = f'cros-logs-{deviceId}-{created}.zip' downloadfilename = os.path.join(targetFolder, downloadfile) dl_url = deviceFile['downloadUrl'] _, content = cd._http.request(dl_url) fileutils.write_file(downloadfilename, content, mode='wb', continue_on_error=True) print(f'Downloaded: {downloadfilename}') elif downloadfile: print('ERROR: no files to download.') cpuStatusReports = _filterCreateReportTime( cros.get('cpuStatusReports', []), 'reportTime', startDate, endDate) lenCSR = len(cpuStatusReports) if lenCSR: print(' cpuStatusReports') num_ranges = min(lenCSR, listLimit or lenCSR) for cpuStatusReport in cpuStatusReports[:num_ranges]: print(f' reportTime: {cpuStatusReport["reportTime"]}') print(' cpuTemperatureInfo') tempInfos = cpuStatusReport.get('cpuTemperatureInfo', []) for tempInfo in tempInfos: temp_label = tempInfo['label'].strip() temperature = tempInfo['temperature'] print(f' {temp_label}: {temperature}') pct_info = cpuStatusReport["cpuUtilizationPercentageInfo"] util = ",".join([str(x) for x in pct_info]) print(f' cpuUtilizationPercentageInfo: {util}') diskVolumeReports = cros.get('diskVolumeReports', []) lenDVR = len(diskVolumeReports) if lenDVR: print(' diskVolumeReports') print(' volumeInfo') num_ranges = min(lenDVR, listLimit or lenDVR) for diskVolumeReport in diskVolumeReports[:num_ranges]: volumeInfo = diskVolumeReport['volumeInfo'] for volume in volumeInfo: vid = volume['volumeId'] vstorage_free = volume['storageFree'] vstorage_total = volume['storageTotal'] print(f' volumeId: {vid}') print(f' storageFree: {vstorage_free}') print(f' storageTotal: {vstorage_total}') systemRamFreeReports = _filterCreateReportTime( cros.get('systemRamFreeReports', []), 'reportTime', startDate, endDate) lenSRFR = len(systemRamFreeReports) if lenSRFR: print(' systemRamFreeReports') num_ranges = min(lenSRFR, listLimit or lenSRFR) for systemRamFreeReport in systemRamFreeReports[:num_ranges]: report_time = systemRamFreeReport["reportTime"] free_info = systemRamFreeReport["systemRamFreeInfo"] free_ram = ",".join(free_info) print(f' reportTime: {report_time}') print(f' systemRamFreeInfo: {free_ram}')
def write_csv_file(csvRows, titles, list_type, todrive): def rowDateTimeFilterMatch(dateMode, rowDate, op, filterDate): if not rowDate or not isinstance(rowDate, str): return False try: rowTime = dateutil.parser.parse(rowDate, ignoretz=True) if dateMode: rowDate = datetime.datetime(rowTime.year, rowTime.month, rowTime.day).isoformat() + 'Z' except ValueError: rowDate = NEVER_TIME if op == '<': return rowDate < filterDate if op == '<=': return rowDate <= filterDate if op == '>': return rowDate > filterDate if op == '>=': return rowDate >= filterDate if op == '!=': return rowDate != filterDate return rowDate == filterDate def rowCountFilterMatch(rowCount, op, filterCount): if isinstance(rowCount, str): if not rowCount.isdigit(): return False rowCount = int(rowCount) elif not isinstance(rowCount, int): return False if op == '<': return rowCount < filterCount if op == '<=': return rowCount <= filterCount if op == '>': return rowCount > filterCount if op == '>=': return rowCount >= filterCount if op == '!=': return rowCount != filterCount return rowCount == filterCount def rowBooleanFilterMatch(rowBoolean, filterBoolean): if not isinstance(rowBoolean, bool): return False return rowBoolean == filterBoolean def headerFilterMatch(title): for filterStr in GC_Values[GC_CSV_HEADER_FILTER]: if filterStr.match(title): return True return False if GC_Values[GC_CSV_ROW_FILTER]: for column, filterVal in iter(GC_Values[GC_CSV_ROW_FILTER].items()): if column not in titles: sys.stderr.write( f'WARNING: Row filter column "{column}" is not in output columns\n' ) continue if filterVal[0] == 'regex': csvRows = [ row for row in csvRows if filterVal[1].search(str(row.get(column, ''))) ] elif filterVal[0] == 'notregex': csvRows = [ row for row in csvRows if not filterVal[1].search(str(row.get(column, ''))) ] elif filterVal[0] in ['date', 'time']: csvRows = [ row for row in csvRows if rowDateTimeFilterMatch( filterVal[0] == 'date', row.get(column, ''), filterVal[1], filterVal[2]) ] elif filterVal[0] == 'count': csvRows = [ row for row in csvRows if rowCountFilterMatch( row.get(column, 0), filterVal[1], filterVal[2]) ] else: #boolean csvRows = [ row for row in csvRows if rowBooleanFilterMatch( row.get(column, False), filterVal[1]) ] if GC_Values[GC_CSV_HEADER_FILTER]: titles = [t for t in titles if headerFilterMatch(t)] if not titles: controlflow.system_error_exit( 3, 'No columns selected with GAM_CSV_HEADER_FILTER\n') return csv.register_dialect('nixstdout', lineterminator='\n') if todrive: write_to = io.StringIO() else: write_to = sys.stdout writer = csv.DictWriter(write_to, fieldnames=titles, dialect='nixstdout', extrasaction='ignore', quoting=csv.QUOTE_MINIMAL) try: writer.writerow(dict((item, item) for item in writer.fieldnames)) writer.writerows(csvRows) except IOError as e: controlflow.system_error_exit(6, e) if todrive: admin_email = __main__._getValueFromOAuth('email') _, drive = __main__.buildDrive3GAPIObject(admin_email) if not drive: print( f'''\nGAM is not authorized to create Drive files. Please run: gam user {admin_email} check serviceaccount and follow recommend steps to authorize GAM for Drive access.''') sys.exit(5) result = gapi.call(drive.about(), 'get', fields='maxImportSizes') columns = len(titles) rows = len(csvRows) cell_count = rows * columns data_size = len(write_to.getvalue()) max_sheet_bytes = int( result['maxImportSizes'][MIMETYPE_GA_SPREADSHEET]) if cell_count > MAX_GOOGLE_SHEET_CELLS or data_size > max_sheet_bytes: print( f'{WARNING_PREFIX}{MESSAGE_RESULTS_TOO_LARGE_FOR_GOOGLE_SPREADSHEET}' ) mimeType = 'text/csv' else: mimeType = MIMETYPE_GA_SPREADSHEET body = { 'description': QuotedArgumentList(sys.argv), 'name': f'{GC_Values[GC_DOMAIN]} - {list_type}', 'mimeType': mimeType } result = gapi.call(drive.files(), 'create', fields='webViewLink', body=body, media_body=googleapiclient.http.MediaInMemoryUpload( write_to.getvalue().encode(), mimetype='text/csv')) file_url = result['webViewLink'] if GC_Values[GC_NO_BROWSER]: msg_txt = f'Drive file uploaded to:\n {file_url}' msg_subj = f'{GC_Values[GC_DOMAIN]} - {list_type}' __main__.send_email(msg_subj, msg_txt) print(msg_txt) else: webbrowser.open(file_url)
def doGetCustomerInfo(): cd = gapi.directory.buildGAPIObject() customer_info = gapi.call(cd.customers(), 'get', customerKey=GC_Values[GC_CUSTOMER_ID]) print(f'Customer ID: {customer_info["id"]}') print(f'Primary Domain: {customer_info["customerDomain"]}') result = gapi.call(cd.domains(), 'get', customer=customer_info['id'], domainName=customer_info['customerDomain'], fields='verified') print(f'Primary Domain Verified: {result["verified"]}') # If customer has changed primary domain customerCreationTime is date # of current primary being added, not customer create date. # We should also get all domains and use oldest date customer_creation = customer_info['customerCreationTime'] date_format = '%Y-%m-%dT%H:%M:%S.%fZ' oldest = datetime.datetime.strptime(customer_creation, date_format) domains = gapi.get_items(cd.domains(), 'list', 'domains', customer=GC_Values[GC_CUSTOMER_ID], fields='domains(creationTime)') for domain in domains: creation_timestamp = int(domain['creationTime']) / 1000 domain_creation = datetime.datetime.fromtimestamp(creation_timestamp) if domain_creation < oldest: oldest = domain_creation print(f'Customer Creation Time: {oldest.strftime(date_format)}') customer_language = customer_info.get('language', 'Unset (defaults to en)') print(f'Default Language: {customer_language}') if 'postalAddress' in customer_info: print('Address:') for field in ADDRESS_FIELDS_PRINT_ORDER: if field in customer_info['postalAddress']: print(f' {field}: {customer_info["postalAddress"][field]}') if 'phoneNumber' in customer_info: print(f'Phone: {customer_info["phoneNumber"]}') print(f'Admin Secondary Email: {customer_info["alternateEmail"]}') user_counts_map = { 'accounts:num_users': 'Total Users', 'accounts:gsuite_basic_total_licenses': 'G Suite Basic Licenses', 'accounts:gsuite_basic_used_licenses': 'G Suite Basic Users', 'accounts:gsuite_enterprise_total_licenses': 'G Suite Enterprise ' \ 'Licenses', 'accounts:gsuite_enterprise_used_licenses': 'G Suite Enterprise ' \ 'Users', 'accounts:gsuite_unlimited_total_licenses': 'G Suite Business ' \ 'Licenses', 'accounts:gsuite_unlimited_used_licenses': 'G Suite Business Users' } parameters = ','.join(list(user_counts_map)) tryDate = datetime.date.today().strftime(YYYYMMDD_FORMAT) customerId = GC_Values[GC_CUSTOMER_ID] if customerId == MY_CUSTOMER: customerId = None rep = gapi.reports.buildGAPIObject() usage = None throw_reasons = [gapi.errors.ErrorReason.INVALID] while True: try: usage = gapi.get_all_pages(rep.customerUsageReports(), 'get', 'usageReports', throw_reasons=throw_reasons, customerId=customerId, date=tryDate, parameters=parameters) break except gapi.errors.GapiInvalidError as e: tryDate = gapi.reports._adjust_date(str(e)) if not usage: print('No user count data available.') return print(f'User counts as of {tryDate}:') for item in usage[0]['parameters']: api_name = user_counts_map.get(item['name']) api_value = int(item.get('intValue', 0)) if api_name and api_value: print(f' {api_name}: {api_value:,}')