def issue_command(): cd = gapi_directory.build() i, devices = getCrOSDeviceEntity(3, cd) body = {} valid_commands = gapi.get_enum_values_minus_unspecified( cd._rootDesc['schemas']['DirectoryChromeosdevicesIssueCommandRequest'] ['properties']['commandType']['enum']) command_map = {} for valid_command in valid_commands: v = valid_command.lower().replace('_', '') command_map[v] = valid_command times_to_check_status = 1 doit = False while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'command': command = sys.argv[i + 1].lower().replace('_', '') if command not in command_map: controlflow.system_error_exit(2, f'expected command of ' \ f'{", ".join(valid_commands)} got {command}') body['commandType'] = command_map[command] i += 2 if command == 'setvolume': body['payload'] = json.dumps({'volume': sys.argv[i]}) i += 1 elif myarg == 'timestocheckstatus': times_to_check_status = int(sys.argv[i + 1]) i += 2 elif myarg == 'doit': doit = True i += 1 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam issuecommand cros') if 'commandType' not in body: controlflow.missing_argument_exit('command <CrOSCommand>', 'gam issuecommand cros') if body['commandType'] == 'WIPE_USERS' and not doit: controlflow.system_error_exit(2, 'wipe_users command requires admin ' \ 'acknowledge user data will be destroyed with the ' \ 'doit argument') if body['commandType'] == 'REMOTE_POWERWASH' and not doit: controlflow.system_error_exit(2, 'remote_powerwash command requires ' \ 'admin acknowledge user data will be destroyed, device will need' \ ' to be reconnected to WiFi and re-enrolled with the doit argument') for device_id in devices: try: result = gapi.call( cd.customer().devices().chromeos(), 'issueCommand', customerId=GC_Values[GC_CUSTOMER_ID], deviceId=device_id, throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O], body=body) except googleapiclient.errors.HttpError: controlflow.system_error_exit(4, '400 response from Google. This ' \ 'usually indicates the devices was not in a state where it will' \ ' accept the command. For example, reboot, set_volume and take_a_screenshot' \ ' require the device to be in auto-start kiosk app mode.') command_id = result.get('commandId') _display_cros_command_result(cd, device_id, command_id, times_to_check_status)
def call(service, function, silent_errors=False, soft_errors=False, throw_reasons=None, retry_reasons=None, **kwargs): """Executes a single request on a Google service function. Args: service: A Google service object for the desired API. function: String, The name of a service request method to execute. silent_errors: Bool, If True, error messages are suppressed when encountered. soft_errors: Bool, If True, writes non-fatal errors to stderr. throw_reasons: A list of Google HTTP error reason strings indicating the errors generated by this request should be re-thrown. All other HTTP errors are consumed. retry_reasons: A list of Google HTTP error reason strings indicating which error should be retried, using exponential backoff techniques, when the error reason is encountered. **kwargs: Additional params to pass to the request method. Returns: A response object for the corresponding Google API call. """ if throw_reasons is None: throw_reasons = [] if retry_reasons is None: retry_reasons = [] method = getattr(service, function) retries = 10 parameters = dict( list(kwargs.items()) + list(GM_Globals[GM_EXTRA_ARGS_DICT].items())) for n in range(1, retries + 1): try: return method(**parameters).execute() except googleapiclient.errors.HttpError as e: http_status, reason, message = errors.get_gapi_error_detail( e, soft_errors=soft_errors, silent_errors=silent_errors, retry_on_http_error=n < 3) if http_status == -1: # The error detail indicated that we should retry this request # We'll refresh credentials and make another pass service._http.credentials.refresh(transport.create_http()) continue if http_status == 0: return None is_known_error_reason = reason in [ r.value for r in errors.ErrorReason ] if is_known_error_reason and errors.ErrorReason( reason) in throw_reasons: if errors.ErrorReason( reason) in errors.ERROR_REASON_TO_EXCEPTION: raise errors.ERROR_REASON_TO_EXCEPTION[errors.ErrorReason( reason)](message) raise e if (n != retries) and (is_known_error_reason and errors.ErrorReason( reason) in errors.DEFAULT_RETRY_REASONS + retry_reasons): controlflow.wait_on_failure(n, retries, reason) continue if soft_errors: display.print_error( f'{http_status}: {message} - {reason}{["", ": Giving up."][n > 1]}' ) return None controlflow.system_error_exit( int(http_status), f'{http_status}: {message} - {reason}') except google.auth.exceptions.RefreshError as e: handle_oauth_token_error( e, soft_errors or errors.ErrorReason.SERVICE_NOT_AVAILABLE in throw_reasons) if errors.ErrorReason.SERVICE_NOT_AVAILABLE in throw_reasons: raise errors.GapiServiceNotAvailableError(str(e)) display.print_error( f'User {GM_Globals[GM_CURRENT_API_USER]}: {str(e)}') return None except ValueError as e: if hasattr(service._http, 'cache') and service._http.cache is not None: service._http.cache = None continue controlflow.system_error_exit(4, str(e)) except (httplib2.ServerNotFoundError, RuntimeError) as e: if n != retries: service._http.connections = {} controlflow.wait_on_failure(n, retries, str(e)) continue controlflow.system_error_exit(4, str(e)) except TypeError as e: controlflow.system_error_exit(4, str(e))
def doUpdateCros(): cd = gapi_directory.build() 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 = gapi_directory_orgunits.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 == 'deprovisionupgradetransfer': action = 'deprovision' deprovisionReason = 'upgrade_transfer' 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, ' \ f'deprovision_upgrade_transfer, 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 = gam.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 = gam.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 get_gapi_error_detail(e, soft_errors=False, silent_errors=False, retry_on_http_error=False): """Extracts error detail from a non-200 GAPI Response. Args: e: googleapiclient.HttpError, The HTTP Error received. soft_errors: Boolean, If true, causes error messages to be surpressed, rather than sending them to stderr. silent_errors: Boolean, If true, suppresses and ignores any errors from being displayed retry_on_http_error: Boolean, If true, will return -1 as the HTTP Response code, indicating that the request can be retried. TODO: Remove this param, as it seems to be outside the scope of this method. Returns: A tuple containing the HTTP Response code, GAPI error reason, and error message. """ try: error = json.loads(e.content.decode(UTF8)) except ValueError: error_content = e.content.decode(UTF8) if isinstance( e.content, bytes) else e.content if (e.resp['status'] == '503') and ( error_content == 'Quota exceeded for the current request'): return (e.resp['status'], ErrorReason.QUOTA_EXCEEDED.value, error_content) if (e.resp['status'] == '403') and (error_content.startswith( 'Request rate higher than configured')): return (e.resp['status'], ErrorReason.QUOTA_EXCEEDED.value, error_content) if (e.resp['status'] == '502') and ('Bad Gateway' in error_content): return (e.resp['status'], ErrorReason.BAD_GATEWAY.value, error_content) if (e.resp['status'] == '504') and ('Gateway Timeout' in error_content): return (e.resp['status'], ErrorReason.GATEWAY_TIMEOUT.value, error_content) if (e.resp['status'] == '403') and ('Invalid domain.' in error_content): error = _create_http_error_dict(403, ErrorReason.NOT_FOUND.value, 'Domain not found') elif (e.resp['status'] == '400') and ( 'InvalidSsoSigningKey' in error_content): error = _create_http_error_dict(400, ErrorReason.INVALID.value, 'InvalidSsoSigningKey') elif (e.resp['status'] == '400') and ('UnknownError' in error_content): error = _create_http_error_dict(400, ErrorReason.INVALID.value, 'UnknownError') elif retry_on_http_error: return (-1, None, None) elif soft_errors: if not silent_errors: display.print_error(error_content) return (0, None, None) else: controlflow.system_error_exit(5, error_content) # END: ValueError catch if 'error' in error: http_status = error['error']['code'] try: message = error['error']['errors'][0]['message'] except KeyError: message = error['error']['message'] if http_status == 404: if 'Requested entity was not found' in message or 'does not exist' in message: error = _create_http_error_dict(404, ErrorReason.NOT_FOUND.value, message) else: if 'error_description' in error: if error['error_description'] == 'Invalid Value': message = error['error_description'] http_status = 400 error = _create_http_error_dict(400, ErrorReason.INVALID.value, message) else: controlflow.system_error_exit(4, str(error)) else: controlflow.system_error_exit(4, str(error)) # Extract the error reason try: reason = error['error']['errors'][0]['reason'] if reason == 'notFound': if 'userKey' in message: reason = ErrorReason.USER_NOT_FOUND.value elif 'groupKey' in message: reason = ErrorReason.GROUP_NOT_FOUND.value elif 'memberKey' in message: reason = ErrorReason.MEMBER_NOT_FOUND.value elif 'Domain not found' in message: reason = ErrorReason.DOMAIN_NOT_FOUND.value elif 'Resource Not Found' in message: reason = ErrorReason.RESOURCE_NOT_FOUND.value elif reason == 'invalid': if 'userId' in message: reason = ErrorReason.USER_NOT_FOUND.value elif 'memberKey' in message: reason = ErrorReason.INVALID_MEMBER.value elif reason == 'failedPrecondition': if 'Bad Request' in message: reason = ErrorReason.BAD_REQUEST.value elif 'Mail service not enabled' in message: reason = ErrorReason.SERVICE_NOT_AVAILABLE.value elif reason == 'required': if 'memberKey' in message: reason = ErrorReason.MEMBER_NOT_FOUND.value elif reason == 'conditionNotMet': if 'Cyclic memberships not allowed' in message: reason = ErrorReason.CYCLIC_MEMBERSHIPS_NOT_ALLOWED.value except KeyError: reason = f'{http_status}' return (http_status, reason, message)
def update_policy(): svc = build() customer = _get_customerid() schemas = build_schemas(svc) orgunit = None printer_id = None app_id = None i = 3 body = {'requests': []} while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg in ['ou', 'org', 'orgunit']: orgunit = _get_orgunit(sys.argv[i + 1]) i += 2 elif myarg == 'printerid': printer_id = sys.argv[i + 1] i += 2 elif myarg == 'appid': app_id = sys.argv[i + 1] i += 2 elif myarg in schemas: body['requests'].append({ 'policyValue': { 'policySchema': schemas[myarg]['name'], 'value': {} }, 'updateMask': '' }) i += 1 while i < len(sys.argv): field = sys.argv[i].lower() if field in ['ou', 'org', 'orgunit', 'printerid', 'appid' ] or '.' in field: break # field is actually a new policy, orgunit or app/printer id expected_fields = ', '.join(schemas[myarg]['settings']) if field not in expected_fields: msg = f'Expected {myarg} field of {expected_fields}. Got {field}.' controlflow.system_error_exit(4, msg) cased_field = schemas[myarg]['settings'][field]['name'] value = sys.argv[i + 1] vtype = schemas[myarg]['settings'][field]['type'] if vtype in ['TYPE_INT64', 'TYPE_INT32', 'TYPE_UINT64']: if not value.isnumeric(): msg = f'Value for {myarg} {field} must be a number, got {value}' controlflow.system_error_exit(7, msg) value = int(value) elif vtype in ['TYPE_BOOL']: value = gam.getBoolean(value, field) elif vtype in ['TYPE_ENUM']: value = value.upper() enum_values = schemas[myarg]['settings'][field]['enums'] if value not in enum_values: expected_enums = ', '.join(enum_values) msg = f'Expected {myarg} {field} value to be one of ' \ f'{expected_enums}, got {value}' controlflow.system_error_exit(8, msg) prefix = schemas[myarg]['settings'][field]['enum_prefix'] value = f'{prefix}{value}' elif vtype in ['TYPE_LIST']: value = value.split(',') body['requests'][-1]['policyValue']['value'][ cased_field] = value body['requests'][-1]['updateMask'] += f'{cased_field},' i += 2 else: msg = f'{myarg} is not a valid argument to "gam update chromepolicy"' controlflow.system_error_exit(4, msg) if not orgunit: controlflow.system_error_exit(3, 'You must specify an orgunit') for request in body['requests']: request['policyTargetKey'] = {'targetResource': orgunit} if printer_id: request['policyTargetKey']['additionalTargetKeys'] = { 'printer_id': printer_id } elif app_id: request['policyTargetKey']['additionalTargetKeys'] = { 'app_id': app_id } gapi.call(svc.customers().policies().orgunits(), 'batchModify', customer=customer, body=body)
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': gam.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 = gam.buildGAPIObject('directory') for account in add_accounts: print(f'adding {account} to hold.') add_body = {'accountId': gam.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 = gam.convertEmailAddressToUID(account, cd) gapi.call(v.matters().holds().accounts(), 'delete', matterId=matterId, holdId=holdId, accountId=accountId)
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': gam.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': gam.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'] = gam.getBoolean( sys.argv[i + 1], myarg) i += 2 elif myarg in ['includerooms']: body['query']['hangoutsChatOptions'] = { 'includeRooms': gam.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 = gam.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'] = gam.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 update(): verif = build() a_domain = sys.argv[3] verificationMethod = sys.argv[4].upper() if verificationMethod == 'CNAME': verificationMethod = 'DNS_CNAME' elif verificationMethod in ['TXT', 'TEXT']: verificationMethod = 'DNS_TXT' if verificationMethod in ['DNS_TXT', 'DNS_CNAME']: verify_type = 'INET_DOMAIN' identifier = a_domain else: verify_type = 'SITE' identifier = f'http://{a_domain}/' body = { 'site': { 'type': verify_type, 'identifier': identifier }, 'verificationMethod': verificationMethod } try: verify_result = gapi.call( verif.webResource(), 'insert', throw_reasons=[gapi_errors.ErrorReason.BAD_REQUEST], verificationMethod=verificationMethod, body=body) except gapi_errors.GapiBadRequestError as e: print(f'ERROR: {str(e)}') verify_data = gapi.call(verif.webResource(), 'getToken', body=body) print(f'Method: {verify_data["method"]}') print(f'Expected Token: {verify_data["token"]}') if verify_data['method'] in ['DNS_CNAME', 'DNS_TXT']: simplehttp = transport.create_http() base_url = 'https://dns.google/resolve?' query_params = {} if verify_data['method'] == 'DNS_CNAME': cname_token = verify_data['token'] cname_list = cname_token.split(' ') cname_subdomain = cname_list[0] query_params['name'] = f'{cname_subdomain}.{a_domain}' query_params['type'] = 'cname' else: query_params['name'] = a_domain query_params['type'] = 'txt' full_url = base_url + urlencode(query_params) (_, c) = simplehttp.request(full_url, 'GET') result = json.loads(c) status = result['Status'] if status == 0 and 'Answer' in result: answers = result['Answer'] if verify_data['method'] == 'DNS_CNAME': answer = answers[0]['data'] else: answer = 'no matching record found' for possible_answer in answers: possible_answer['data'] = possible_answer[ 'data'].strip('"') if possible_answer['data'].startswith( 'google-site-verification'): answer = possible_answer['data'] break print( f'Unrelated TXT record: {possible_answer["data"]}') print(f'Found DNS Record: {answer}') elif status == 0: controlflow.system_error_exit(1, 'DNS record not found') else: controlflow.system_error_exit( status, DNS_ERROR_CODES_MAP.get(status, f'Unknown error {status}')) return print('SUCCESS!') print(f'Verified: {verify_result["site"]["identifier"]}') print(f'ID: {verify_result["id"]}') print(f'Type: {verify_result["site"]["type"]}') print('All Owners:') try: for owner in verify_result['owners']: print(f' {owner}') except KeyError: pass print() print( f'You can now add {a_domain} or it\'s subdomains as secondary or domain aliases of the {GC_Values[GC_DOMAIN]} G Suite Account.' )
def update_policy(): svc = build() customer = _get_customerid() schemas = build_schemas(svc) orgunit = None printer_id = None app_id = None i = 3 body = {'requests': []} while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg in ['ou', 'org', 'orgunit']: orgunit = _get_orgunit(sys.argv[i+1]) i += 2 elif myarg == 'printerid': printer_id = sys.argv[i+1] i += 2 elif myarg == 'appid': app_id = sys.argv[i+1] i += 2 elif myarg in schemas: schemaName = schemas[myarg]['name'] body['requests'].append({'policyValue': {'policySchema': schemaName, 'value': {}}, 'updateMask': ''}) i += 1 while i < len(sys.argv): field = sys.argv[i].lower() if field in ['ou', 'org', 'orgunit', 'printerid', 'appid'] or '.' in field: break # field is actually a new policy, orgunit or app/printer id # Handle TYPE_MESSAGE fields with durations, values, counts and timeOfDay as special cases schema = CHROME_SCHEMA_TYPE_MESSAGE.get(schemaName, {}).get(field) if schema: i += 1 casedField = schema['casedField'] vtype = schema['type'] if vtype != 'timeOfDay': if 'default' not in schema: value = gam.getInteger(sys.argv[i], casedField, minVal=schema['minVal'], maxVal=schema['maxVal'])*schema['scale'] i += 1 elif i < len(sys.argv) and sys.argv[i].isdigit(): value = gam.getInteger(sys.argv[i], casedField, minVal=schema['minVal'], maxVal=schema['maxVal'])*schema['scale'] i += 1 else: # Handle empty value for fields with default value = schema['default']*schema['scale'] if i < len(sys.argv) and not sys.argv[i]: i += 1 else: value = utils.get_hhmm(sys.argv[i]) i += 1 if vtype == 'duration': body['requests'][-1]['policyValue']['value'][casedField] = {vtype: f'{value}s'} elif vtype == 'value': body['requests'][-1]['policyValue']['value'][casedField] = {vtype: value} elif vtype == 'count': body['requests'][-1]['policyValue']['value'][casedField] = value else: ##timeOfDay hours, minutes = value.split(':') body['requests'][-1]['policyValue']['value'][casedField] = {vtype: {'hours': hours, 'minutes': minutes}} body['requests'][-1]['updateMask'] += f'{casedField},' continue expected_fields = ', '.join(schemas[myarg]['settings']) if field not in expected_fields: msg = f'Expected {myarg} field of {expected_fields}. Got {field}.' controlflow.system_error_exit(4, msg) cased_field = schemas[myarg]['settings'][field]['name'] value = sys.argv[i+1] vtype = schemas[myarg]['settings'][field]['type'] if vtype in ['TYPE_INT64', 'TYPE_INT32', 'TYPE_UINT64']: if not value.isnumeric(): msg = f'Value for {myarg} {field} must be a number, got {value}' controlflow.system_error_exit(7, msg) value = int(value) elif vtype in ['TYPE_BOOL']: value = gam.getBoolean(value, field) elif vtype in ['TYPE_ENUM']: value = value.upper() prefix = schemas[myarg]['settings'][field]['enum_prefix'] enum_values = schemas[myarg]['settings'][field]['enums'] if value in enum_values: value = f'{prefix}{value}' elif value.replace(prefix, '') in enum_values: pass else: expected_enums = ', '.join(enum_values) msg = f'Expected {myarg} {field} value to be one of ' \ f'{expected_enums}, got {value}' controlflow.system_error_exit(8, msg) elif vtype in ['TYPE_LIST']: value = value.split(',') if myarg == 'chrome.users.chromebrowserupdates' and \ cased_field == 'targetVersionPrefixSetting': mg = re.compile(r'^([a-z]+)-(\d+)$').match(value) if mg: channel = mg.group(1).lower().replace('_', '') minus = mg.group(2) channel_map = gapi_chromehistory.get_channel_map(None) if channel not in channel_map: expected_channels = ', '.join(channel_map) msg = f'Expected {myarg} {cased_field} channel to be one of ' \ f'{expected_channels}, got {channel}' controlflow.system_error_exit(8, msg) milestone = gapi_chromehistory.get_relative_milestone( channel_map[channel], int(minus)) if not milestone: msg = f'{myarg} {cased_field} channel {channel} offset {minus} does not exist' controlflow.system_error_exit(8, msg) value = f'{milestone}.' body['requests'][-1]['policyValue']['value'][cased_field] = value body['requests'][-1]['updateMask'] += f'{cased_field},' i += 2 else: msg = f'{myarg} is not a valid argument to "gam update chromepolicy"' controlflow.system_error_exit(4, msg) if not orgunit: controlflow.system_error_exit(3, 'You must specify an orgunit') for request in body['requests']: request['policyTargetKey'] = {'targetResource': orgunit} if printer_id: request['policyTargetKey']['additionalTargetKeys'] = {'printer_id': printer_id} elif app_id: request['policyTargetKey']['additionalTargetKeys'] = {'app_id': app_id} gapi.call(svc.customers().policies().orgunits(), 'batchModify', customer=customer, body=body)
def print_(): ci = gapi_cloudidentity.build('cloudidentity_beta') i = 3 members = membersCountOnly = managers = managersCountOnly = owners = ownersCountOnly = False gapi_directory_customer.setTrueCustomerId() parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}' usemember = None memberDelimiter = '\n' todrive = False titles = [] csvRows = [] roles = [] sortHeaders = False while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg == 'todrive': todrive = True i += 1 elif myarg == 'enterprisemember': member = gam.convertUIDtoEmailAddress(sys.argv[i + 1], email_types=['user', 'group']) usemember = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels" i += 2 elif myarg == 'delimiter': memberDelimiter = sys.argv[i + 1] i += 2 elif myarg == 'sortheaders': sortHeaders = True i += 1 elif myarg in ['members', 'memberscount']: roles.append(ROLE_MEMBER) members = True if myarg == 'memberscount': membersCountOnly = True i += 1 elif myarg in ['owners', 'ownerscount']: roles.append(ROLE_OWNER) owners = True if myarg == 'ownerscount': ownersCountOnly = True i += 1 elif myarg in ['managers', 'managerscount']: roles.append(ROLE_MANAGER) managers = True if myarg == 'managerscount': managersCountOnly = True i += 1 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam print cigroups') if roles: if members: display.add_titles_to_csv_file([ 'MembersCount', ], titles) if not membersCountOnly: display.add_titles_to_csv_file([ 'Members', ], titles) if managers: display.add_titles_to_csv_file([ 'ManagersCount', ], titles) if not managersCountOnly: display.add_titles_to_csv_file([ 'Managers', ], titles) if owners: display.add_titles_to_csv_file([ 'OwnersCount', ], titles) if not ownersCountOnly: display.add_titles_to_csv_file([ 'Owners', ], titles) gam.printGettingAllItems('Groups', usemember) page_message = gapi.got_total_items_first_last_msg('Groups') if usemember: try: result = gapi.get_all_pages(ci.groups().memberships(), 'searchTransitiveGroups', 'memberships', throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O], page_message=page_message, message_attribute=['groupKey', 'id'], parent='groups/-', query=usemember, fields='nextPageToken,memberships(group,groupKey(id),relationType)', pageSize=1000) except googleapiclient.errors.HttpError: controlflow.system_error_exit( 2, f'enterprisemember requires Enterprise license') entityList = [] for entity in result: if entity['relationType'] == 'DIRECT': entityList.append(gapi.call(ci.groups(), 'get', name=entity['group'])) else: entityList = gapi.get_all_pages(ci.groups(), 'list', 'groups', page_message=page_message, message_attribute=['groupKey', 'id'], parent=parent, view='FULL', pageSize=500) i = 0 count = len(entityList) for groupEntity in entityList: i += 1 groupEmail = groupEntity['groupKey']['id'] for k, v in iter(groupEntity.pop('labels', {}).items()): if v == '': groupEntity[f'labels.{k}'] = True else: groupEntity[f'labels.{k}'] = v group = utils.flatten_json(groupEntity) for a_key in group: if a_key not in titles: titles.append(a_key) groupKey_id = groupEntity['name'] if roles: sys.stderr.write( f' Getting {roles} for {groupEmail}{gam.currentCountNL(i, count)}' ) page_message = gapi.got_total_items_first_last_msg('Members') validRoles, _, _ = gam._getRoleVerification( '.'.join(roles), 'nextPageToken,members(email,id,role)') groupMembers = gapi.get_all_pages(ci.groups().memberships(), 'list', 'memberships', page_message=page_message, message_attribute=['memberKey', 'id'], soft_errors=True, parent=groupKey_id, view='BASIC') if members: membersList = [] membersCount = 0 if managers: managersList = [] managersCount = 0 if owners: ownersList = [] ownersCount = 0 for member in groupMembers: member_email = member['memberKey']['id'] role = get_single_role(member.get('roles')) if not validRoles or role in validRoles: if role == ROLE_MEMBER: if members: membersCount += 1 if not membersCountOnly: membersList.append(member_email) elif role == ROLE_MANAGER: if managers: managersCount += 1 if not managersCountOnly: managersList.append(member_email) elif role == ROLE_OWNER: if owners: ownersCount += 1 if not ownersCountOnly: ownersList.append(member_email) elif members: membersCount += 1 if not membersCountOnly: membersList.append(member_email) if members: group['MembersCount'] = membersCount if not membersCountOnly: group['Members'] = memberDelimiter.join(membersList) if managers: group['ManagersCount'] = managersCount if not managersCountOnly: group['Managers'] = memberDelimiter.join(managersList) if owners: group['OwnersCount'] = ownersCount if not ownersCountOnly: group['Owners'] = memberDelimiter.join(ownersList) csvRows.append(group) if sortHeaders: display.sort_csv_titles([ 'name', 'groupKey.id' ], titles) display.write_csv_file(csvRows, titles, 'Groups', todrive)
def print_members(): ci = gapi_cloudidentity.build('cloudidentity_beta') todrive = False gapi_directory_customer.setTrueCustomerId() parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}' usemember = None roles = [] titles = ['group'] csvRows = [] groups_to_get = [] i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'todrive': todrive = True i += 1 elif myarg in ['role', 'roles']: for role in sys.argv[i + 1].lower().replace(',', ' ').split(): if role in GROUP_ROLES_MAP: roles.append(GROUP_ROLES_MAP[role]) else: controlflow.system_error_exit( 2, f'{role} is not a valid role for "gam print group-members {myarg}"' ) i += 2 elif myarg == 'enterprisemember': member = gam.convertUIDtoEmailAddress(sys.argv[i + 1], email_types=['user', 'group']) usemember = f"member_key_id == '{member}' && 'cloudidentity.googleapis.com/groups.discussion_forum' in labels" i += 2 elif myarg in ['cigroup', 'cigroups']: group_email = gam.normalizeEmailAddressOrUID(sys.argv[i + 1]) groups_to_get = [group_email] i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam print cigroup-members') if not groups_to_get: gam.printGettingAllItems('Groups', usemember) page_message = gapi.got_total_items_first_last_msg('Groups') if usemember: try: groups_to_get = gapi.get_all_pages(ci.groups().memberships(), 'searchTransitiveGroups', 'memberships', throw_reasons=[gapi_errors.ErrorReason.FOUR_O_O], message_attribute=['groupKey', 'id'], page_message=page_message, parent='groups/-', query=usemember, pageSize=1000, fields='nextPageToken,memberships(groupKey(id),relationType)') except googleapiclient.errors.HttpError: controlflow.system_error_exit( 2, f'enterprisemember requires Enterprise license') groups_to_get = [group['groupKey']['id'] for group in groups_to_get if group['relationType'] == 'DIRECT'] else: groups_to_get = gapi.get_all_pages( ci.groups(), 'list', 'groups', message_attribute=['groupKey', 'id'], page_message=page_message, parent=parent, view='BASIC', pageSize=1000, fields='nextPageToken,groups(groupKey(id))') groups_to_get = [group['groupKey']['id'] for group in groups_to_get] i = 0 count = len(groups_to_get) for group_email in groups_to_get: i += 1 sys.stderr.write( f'Getting members for {group_email}{gam.currentCountNL(i, count)}') group_id = group_email_to_id(ci, group_email) print(f'Getting members of cigroup {group_email}...') page_message = f' {gapi.got_total_items_first_last_msg("Members")}' group_members = gapi.get_all_pages( ci.groups().memberships(), 'list', 'memberships', soft_errors=True, parent=group_id, view='FULL', pageSize=500, page_message=page_message, message_attribute=['memberKey', 'id']) #fields='nextPageToken,memberships(memberKey,roles,createTime,updateTime)') if roles: group_members = filter_members_to_roles(group_members, roles) for member in group_members: # reduce role to a single value member['role'] = get_single_role(member.pop('roles')) member = utils.flatten_json(member) for title in member: if title not in titles: titles.append(title) member['group'] = group_email csvRows.append(member) display.write_csv_file(csvRows, titles, 'Group Members', todrive)
def printVersions(): cm = build() customer = _get_customerid() todrive = False titles = CHROME_VERSIONS_TITLES csvRows = [] orgunit = None startDate = None endDate = None pfilter = None reverse = False i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'todrive': todrive = True i += 1 elif myarg in ['ou', 'org', 'orgunit']: orgunit = _get_orgunit(sys.argv[i + 1]) i += 2 elif myarg in CROS_START_ARGUMENTS: startDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT) i += 2 elif myarg in CROS_END_ARGUMENTS: endDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT) i += 2 elif myarg == 'recentfirst': reverse = True i += 1 else: msg = f'{myarg} is not a valid argument to "gam print chromeversions"' controlflow.system_error_exit(3, msg) if endDate: pfilter = f'last_active_date<={endDate}' if startDate: if pfilter: pfilter += ' AND ' else: pfilter = '' pfilter += f'last_active_date>={startDate}' if orgunit: orgUnitPath = gapi_directory_orgunits.orgunit_from_orgunitid( orgunit, None) titles.append('orgUnitPath') else: orgUnitPath = '/' gam.printGettingAllItems('Chrome Versions', pfilter) page_message = gapi.got_total_items_msg('Chrome Versions', '...\n') versions = gapi.get_all_pages(cm.customers().reports(), 'countChromeVersions', 'browserVersions', page_message=page_message, customer=customer, orgUnitId=orgunit, filter=pfilter) for version in sorted(versions, key=lambda k: k.get('version', 'Unknown'), reverse=reverse): if orgunit: version['orgUnitPath'] = orgUnitPath if 'version' not in version: version['version'] = 'Unknown' csvRows.append(version) display.write_csv_file(csvRows, titles, 'Chrome Versions', todrive)
def printAppDevices(): cm = build() customer = _get_customerid() todrive = False titles = CHROME_APP_DEVICES_TITLES csvRows = [] orgunit = None appId = None appType = None startDate = None endDate = None pfilter = None orderBy = None i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'todrive': todrive = True i += 1 elif myarg in ['ou', 'org', 'orgunit']: orgunit = _get_orgunit(sys.argv[i + 1]) i += 2 elif myarg == 'appid': appId = sys.argv[i + 1] i += 2 elif myarg == 'apptype': appType = sys.argv[i + 1].lower().replace('_', '') if appType not in CHROME_APP_DEVICES_APPTYPE_CHOICE_MAP: controlflow.expected_argument_exit( 'orderby', ', '.join(CHROME_APP_DEVICES_APPTYPE_CHOICE_MAP), appType) appType = CHROME_APP_DEVICES_APPTYPE_CHOICE_MAP[appType] i += 2 elif myarg in CROS_START_ARGUMENTS: startDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT) i += 2 elif myarg in CROS_END_ARGUMENTS: endDate = _getFilterDate(sys.argv[i + 1]).strftime(YYYYMMDD_FORMAT) i += 2 elif myarg == 'orderby': orderBy = sys.argv[i + 1].lower().replace('_', '') if orderBy not in CHROME_APP_DEVICES_ORDERBY_CHOICE_MAP: controlflow.expected_argument_exit( 'orderby', ', '.join(CHROME_APP_DEVICES_ORDERBY_CHOICE_MAP), orderBy) orderBy = CHROME_APP_DEVICES_ORDERBY_CHOICE_MAP[orderBy] i += 2 else: msg = f'{myarg} is not a valid argument to "gam print chromeappdevices"' controlflow.system_error_exit(3, msg) if not appId: controlflow.system_error_exit(3, 'You must specify an appid') if not appType: controlflow.system_error_exit(3, 'You must specify an apptype') if endDate: pfilter = f'last_active_date<={endDate}' if startDate: if pfilter: pfilter += ' AND ' else: pfilter = '' pfilter += f'last_active_date>={startDate}' if orgunit: orgUnitPath = gapi_directory_orgunits.orgunit_from_orgunitid( orgunit, None) titles.append('orgUnitPath') else: orgUnitPath = '/' gam.printGettingAllItems('Chrome Installed Application Devices', pfilter) page_message = gapi.got_total_items_msg( 'Chrome Installed Application Devices', '...\n') devices = gapi.get_all_pages(cm.customers().reports(), 'findInstalledAppDevices', 'devices', page_message=page_message, appId=appId, appType=appType, customer=customer, orgUnitId=orgunit, filter=pfilter, orderBy=orderBy) for device in devices: if orgunit: device['orgUnitPath'] = orgUnitPath device['appId'] = appId device['appType'] = appType csvRows.append(device) display.write_csv_file(csvRows, titles, 'Chrome Installed Application Devices', todrive)
def printshow_policies(): svc = build() customer = _get_customerid() orgunit = None printer_id = None app_id = None i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg in ['ou', 'org', 'orgunit']: orgunit = _get_orgunit(sys.argv[i + 1]) i += 2 elif myarg == 'printerid': printer_id = sys.argv[i + 1] i += 2 elif myarg == 'appid': app_id = sys.argv[i + 1] i += 2 else: msg = f'{myarg} is not a valid argument to "gam print chromepolicy"' controlflow.system_error_exit(3, msg) if not orgunit: controlflow.system_error_exit(3, 'You must specify an orgunit') body = { 'policyTargetKey': { 'targetResource': orgunit, } } if printer_id: body['policyTargetKey']['additionalTargetKeys'] = { 'printer_id': printer_id } namespaces = ['chrome.printers'] elif app_id: body['policyTargetKey']['additionalTargetKeys'] = {'app_id': app_id} namespaces = [ 'chrome.users.apps', 'chrome.devices.managedGuest.apps', 'chrome.devices.kiosk.apps' ] else: namespaces = [ 'chrome.users', # Not yet implemented: # 'chrome.devices', # 'chrome.devices.managedGuest', # 'chrome.devices.kiosk', ] throw_reasons = [ gapi_errors.ErrorReason.FOUR_O_O, ] orgunitPath = gapi_directory_orgunits.orgunit_from_orgunitid( orgunit[9:], None) header = f'Organizational Unit: {orgunitPath}' if printer_id: header += f', printerid: {printer_id}' elif app_id: header += f', appid: {app_id}' print(header) for namespace in namespaces: body['policySchemaFilter'] = f'{namespace}.*' try: policies = gapi.get_all_pages(svc.customers().policies(), 'resolve', items='resolvedPolicies', throw_reasons=throw_reasons, customer=customer, body=body) except googleapiclient.errors.HttpError: policies = [] for policy in sorted( policies, key=lambda k: k.get('value', {}).get('policySchema', '')): print() name = policy.get('value', {}).get('policySchema', '') print(name) values = policy.get('value', {}).get('value', {}) for setting, value in values.items(): if isinstance(value, str) and value.find('_ENUM_') != -1: value = value.split('_ENUM_')[-1] print(f' {setting}: {value}')
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': gam.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 = gam.buildGAPIObject('directory') account_type = 'group' if body['corpus'] == 'GROUPS' else 'user' for account in accounts: body['accounts'].append({ 'accountId': gam.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 printshow_policies(): svc = build() customer = _get_customerid() orgunit = None printer_id = None app_id = None body = {} namespaces = [] i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg in ['ou', 'org', 'orgunit']: orgunit = _get_orgunit(sys.argv[i+1]) i += 2 elif myarg == 'printerid': printer_id = sys.argv[i+1] i += 2 elif myarg == 'appid': app_id = sys.argv[i+1] i += 2 elif myarg == 'namespace': namespaces.extend(sys.argv[i+1].replace(',', ' ').split()) i += 2 else: msg = f'{myarg} is not a valid argument to "gam print chromepolicy"' controlflow.system_error_exit(3, msg) if not orgunit: controlflow.system_error_exit(3, 'You must specify an orgunit') body['policyTargetKey'] = {'targetResource': orgunit} if printer_id: body['policyTargetKey']['additionalTargetKeys'] = {'printer_id': printer_id} if not namespaces: namespaces = ['chrome.printers'] elif app_id: body['policyTargetKey']['additionalTargetKeys'] = {'app_id': app_id} if not namespaces: namespaces = ['chrome.users.apps', 'chrome.devices.managedGuest.apps', 'chrome.devices.kiosk.apps'] elif not namespaces: namespaces = [ 'chrome.users', 'chrome.users.apps', 'chrome.devices', 'chrome.devices.kiosk', 'chrome.devices.managedGuest', ] throw_reasons = [gapi_errors.ErrorReason.FOUR_O_O,] orgunitPath = gapi_directory_orgunits.orgunit_from_orgunitid(orgunit[9:], None) print(f'Organizational Unit: {orgunitPath}') for namespace in namespaces: spacing = ' ' body['policySchemaFilter'] = f'{namespace}.*' body['pageToken'] = None try: policies = gapi.get_all_pages(svc.customers().policies(), 'resolve', items='resolvedPolicies', throw_reasons=throw_reasons, customer=customer, body=body, page_args_in_body=True) except googleapiclient.errors.HttpError: policies = [] # sort policies first by app/printer id then by schema name policies = sorted(policies, key=lambda k: ( list(k.get('targetKey', {}).get('additionalTargetKeys', {}).values()), k.get('value', {}).get('policySchema', ''))) printed_ids = [] for policy in policies: print() name = policy.get('value', {}).get('policySchema', '') for key, val in policy['targetKey'].get('additionalTargetKeys', {}).items(): additional_id = f'{key} - {val}' if additional_id not in printed_ids: print(f' {additional_id}') printed_ids.append(additional_id) spacing = ' ' print(f'{spacing}{name}') values = policy.get('value', {}).get('value', {}) for setting, value in values.items(): # Handle TYPE_MESSAGE fields with durations, values, counts and timeOfDay as special cases schema = CHROME_SCHEMA_TYPE_MESSAGE.get(name, {}).get(setting.lower()) if schema and setting == schema['casedField']: vtype = schema['type'] if vtype in {'duration', 'value'}: value = value.get(vtype, '') if value: if value.endswith('s'): value = value[:-1] value = int(value) // schema['scale'] elif vtype == 'count': pass else: ##timeOfDay hours = value.get(vtype, {}).get('hours', 0) minutes = value.get(vtype, {}).get('minutes', 0) value = f'{hours:02}:{minutes:02}' elif isinstance(value, str) and value.find('_ENUM_') != -1: value = value.split('_ENUM_')[-1] print(f'{spacing}{setting}: {value}')
def getMatterItem(v, nameOrID): matterId = convertMatterNameToID(v, nameOrID) if not matterId: controlflow.system_error_exit(4, f'could not find matter {nameOrID}') return matterId
def print_count(): v = buildGAPIObject() query_discovery = v._rootDesc['schemas']['Query'] matterId = None operation_wait = 15 query = None body = {'view': 'ALL'} name = None todrive = False i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'matter': matterId = getMatterItem(v, sys.argv[i + 1]) i += 2 elif myarg == 'operation': name = sys.argv[i + 1] i += 2 elif myarg == 'todrive': todrive = True i += 1 elif myarg in QUERY_ARGS: query, i = _build_query(query, myarg, i, query_discovery) elif myarg == 'wait': operation_wait = int(sys.argv[i + 1]) 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 count.') if name: operation = {'name': name} else: _validate_query(query, query_discovery) body['query'] = query operation = gapi.call(v.matters(), 'count', matterId=matterId, body=body) print(f'Watching operation {operation["name"]}...') while not operation.get('done'): print( f' operation {operation["name"]} is not done yet. Checking again in {operation_wait} seconds' ) sleep(operation_wait) operation = gapi.call(v.operations(), 'get', name=operation['name']) response = operation.get('response', {}) query = operation['metadata']['query'] search_method = query.get('searchMethod') # ARGH count results don't include accounts with zero items. # so we keep track of which accounts we searched and can report # zero data for them. if search_method == 'ACCOUNT': query_accounts = query.get('accountInfo', []) elif search_method == 'ENTIRE_ORG': query_accounts = gam.getUsersToModify('all', 'users') elif search_method == 'ORG_UNIT': org_unit = query['orgUnitInfo']['orgUnitId'] query_accounts = gam.getUsersToModify('ou', org_unit) mailcounts = response.get('mailCountResult', {}) groupcounts = response.get('groupsCountResult', {}) csv_rows = [] for a_count in [mailcounts, groupcounts]: for errored_account in a_count.get('accountCountErrors', []): account = errored_account.get('account') csv_rows.append({ 'account': account, 'error': errored_account.get('errorType') }) if account in query_accounts: query_accounts.remove(account) for account in a_count.get('nonQueryableAccounts', []): csv_rows.append({ 'account': account, 'error': 'Not queried because not on hold' }) if account in query_accounts: query_accounts.remove(account) for account in a_count.get('accountCounts', []): email = account.get('account', {}).get('email', '') csv_rows.append({'account': email, 'count': account.get('count')}) if email in query_accounts: query_accounts.remove(email) for account in query_accounts: csv_rows.append({'account': account, 'count': 0}) titles = ['account', 'count', 'error'] display.write_csv_file(csv_rows, titles, 'Vault Counts', todrive)
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 = gam.buildGAPIObject('directory') add_collaborators.extend(validateCollaborators( sys.argv[i + 1], cd)) i += 2 elif myarg in ['removecollaborator', 'removecollaborators']: if not cd: cd = gam.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 _build_query(query, myarg, i, query_discovery): if not query: query = {'dataScope': 'ALL_DATA'} if myarg == 'corpus': query['corpus'] = sys.argv[i + 1].upper() allowed_corpuses = gapi.get_enum_values_minus_unspecified( query_discovery['properties']['corpus']['enum']) if 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 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] query['searchMethod'] = searchMethod if searchMethod == 'ACCOUNT': query['accountInfo'] = {'emails': sys.argv[i + 1].split(',')} i += 2 elif searchMethod == 'ORG_UNIT': query['orgUnitInfo'] = { 'orgUnitId': gapi_directory_orgunits.getOrgUnitId(sys.argv[i + 1])[1] } i += 2 elif searchMethod == 'SHARED_DRIVE': query['sharedDriveInfo'] = { 'sharedDriveIds': sys.argv[i + 1].split(',') } i += 2 elif searchMethod == 'ROOM': query['hangoutsChatInfo'] = {'roomId': sys.argv[i + 1].split(',')} i += 2 else: i += 1 elif myarg == 'scope': query['dataScope'] = sys.argv[i + 1].upper() allowed_scopes = gapi.get_enum_values_minus_unspecified( query_discovery['properties']['dataScope']['enum']) if query['dataScope'] not in allowed_scopes: controlflow.expected_argument_exit('scope', ', '.join(allowed_scopes), sys.argv[i + 1]) i += 2 elif myarg in ['terms']: query['terms'] = sys.argv[i + 1] i += 2 elif myarg in ['start', 'starttime']: query['startTime'] = utils.get_date_zero_time_or_full_time(sys.argv[i + 1]) i += 2 elif myarg in ['end', 'endtime']: query['endTime'] = utils.get_date_zero_time_or_full_time(sys.argv[i + 1]) i += 2 elif myarg in ['timezone']: query['timeZone'] = sys.argv[i + 1] i += 2 elif myarg in ['excludedrafts']: query['mailOptions'] = { 'excludeDrafts': gam.getBoolean(sys.argv[i + 1], myarg) } i += 2 elif myarg in ['driveversiondate']: query.setdefault('driveOptions', {})['versionDate'] = \ utils.get_date_zero_time_or_full_time(sys.argv[i+1]) i += 2 elif myarg in ['includeshareddrives', 'includeteamdrives']: query.setdefault('driveOptions', {})['includeSharedDrives'] = gam.getBoolean( sys.argv[i + 1], myarg) i += 2 elif myarg in ['includerooms']: query['hangoutsChatOptions'] = { 'includeRooms': gam.getBoolean(sys.argv[i + 1], myarg) } i += 2 return (query, i)
def get_delta_time(argstr): deltaTime = get_delta(argstr, DELTA_TIME_PATTERN) if deltaTime is None: controlflow.system_error_exit( 2, f'expected a <{DELTA_TIME_FORMAT_REQUIRED}>; got {argstr}') return deltaTime
def move(): cbcm = build() body = {'resource_ids': []} i = 3 resource_ids = [] batch_size = 600 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'ids': resource_ids.extend(sys.argv[i + 1].split(',')) i += 2 elif myarg == 'query': query = sys.argv[i + 1] page_message = gapi.got_total_items_msg('Browsers', '...\n') browsers = gapi.get_all_pages( cbcm.chromebrowsers(), 'list', 'browsers', page_message=page_message, customer=GC_Values[GC_CUSTOMER_ID], query=query, projection='BASIC', fields='browsers(deviceId),nextPageToken') ids = [browser['deviceId'] for browser in browsers] resource_ids.extend(ids) i += 2 elif myarg == 'file': with fileutils.open_file(sys.argv[i + 1], strip_utf_bom=True) as filed: for row in filed: rid = row.strip() if rid: resource_ids.append(rid) i += 2 elif myarg == 'csvfile': drive, fname_column = os.path.splitdrive(sys.argv[i + 1]) if fname_column.find(':') == -1: controlflow.system_error_exit( 2, 'Expected csvfile FileName:FieldName') (filename, column) = fname_column.split(':') with fileutils.open_file(drive + filename) as filed: input_file = csv.DictReader(filed, restval='') if column not in input_file.fieldnames: controlflow.csv_field_error_exit(column, input_file.fieldnames) for row in input_file: rid = row[column].strip() if rid: resource_ids.append(rid) i += 2 elif myarg in ['ou', 'orgunit', 'org']: org_unit = gapi_directory_orgunits.getOrgUnitItem(sys.argv[i + 1]) body['org_unit_path'] = org_unit i += 2 elif myarg == 'batchsize': batch_size = int(sys.argv[i + 1]) i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam move browsers') if 'org_unit_path' not in body: controlflow.missing_argument_exit('ou', 'gam move browsers') elif not resource_ids: controlflow.missing_argument_exit('query or ids', 'gam move browsers') # split moves into max 600 devices per batch for chunk in range(0, len(resource_ids), batch_size): body['resource_ids'] = resource_ids[chunk:chunk + batch_size] print(f' moving {len(body["resource_ids"])} browsers to ' \ f'{body["org_unit_path"]}') gapi.call(cbcm.chromebrowsers(), 'moveChromeBrowsersToOu', customer=GC_Values[GC_CUSTOMER_ID], body=body)
def createExport(): v = buildGAPIObject() query_discovery = v._rootDesc['schemas']['Query'] 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 query = None useNewExport = None body = {'exportOptions': {}} i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'matter': matterId = getMatterItem(v, sys.argv[i + 1], state='OPEN') body['matterId'] = matterId i += 2 elif myarg == 'name': body['name'] = sys.argv[i + 1] i += 2 elif myarg in QUERY_ARGS: query, i = _build_query(query, myarg, i, query_discovery) elif myarg == 'usenewexport': useNewExport = gam.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 = gam.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'] = gam.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.') _validate_query(query, query_discovery) body['query'] = query 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 if useNewExport is not None: body['exportOptions'][options_field]['useNewExport'] = useNewExport results = gapi.call(v.matters().exports(), 'create', matterId=matterId, body=body) print(f'Created export {results["id"]}') display.print_json(results)
def print_members(): ci = gapi_cloudidentity.build() todrive = False gapi_directory_customer.setTrueCustomerId() parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}' roles = [] titles = ['group'] csvRows = [] groups_to_get = [] i = 3 while i < len(sys.argv): myarg = sys.argv[i].lower().replace('_', '') if myarg == 'todrive': todrive = True i += 1 elif myarg in ['role', 'roles']: for role in sys.argv[i + 1].lower().replace(',', ' ').split(): if role in GROUP_ROLES_MAP: roles.append(GROUP_ROLES_MAP[role]) else: controlflow.system_error_exit( 2, f'{role} is not a valid role for "gam print group-members {myarg}"' ) i += 2 elif myarg in ['cigroup', 'cigroups']: group_email = gam.normalizeEmailAddressOrUID(sys.argv[i + 1]) groups_to_get = [group_email] i += 2 else: controlflow.invalid_argument_exit(sys.argv[i], 'gam print cigroup-members') if not groups_to_get: gam.printGettingAllItems('Groups', None) page_message = gapi.got_total_items_first_last_msg('Groups') groups_to_get = gapi.get_all_pages( ci.groups(), 'list', 'groups', message_attribute=['groupKey', 'id'], page_message=page_message, parent=parent, view='BASIC', pageSize=1000, fields='nextPageToken,groups(groupKey(id))') groups_to_get = [group['groupKey']['id'] for group in groups_to_get] i = 0 count = len(groups_to_get) for group_email in groups_to_get: i += 1 sys.stderr.write( f'Getting members for {group_email}{gam.currentCountNL(i, count)}') group_id = group_email_to_id(ci, group_email) print(f'Getting members of cigroup {group_email}...') page_message = f' {gapi.got_total_items_first_last_msg("Members")}' group_members = gapi.get_all_pages( ci.groups().memberships(), 'list', 'memberships', soft_errors=True, parent=group_id, view='FULL', pageSize=500, page_message=page_message, message_attribute=['memberKey', 'id']) #fields='nextPageToken,memberships(memberKey,roles,createTime,updateTime)') if roles: group_members = filter_members_to_roles(group_members, roles) for member in group_members: # reduce role to a single value member['role'] = get_single_role(member.pop('roles')) member = utils.flatten_json(member) for title in member: if title not in titles: titles.append(title) member['group'] = group_email csvRows.append(member) display.write_csv_file(csvRows, titles, 'Group Members', todrive)