diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 60c4b750..e2d82146 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -283,6 +283,7 @@ jobs: $gam print users query "travis.jid=$JID" | $gam csv - gam delete user ~primaryEmail $gam print mobile $gam print devices + $gam print browsers export sn="$JID$JID$JID$JID-$(openssl rand -base64 32 | sed 's/[^a-zA-Z0-9]//g')" $gam create device serialnumber $sn devicetype android $gam print cros allfields nolists diff --git a/src/cbcm-v1.1beta1.json b/src/cbcm-v1.1beta1.json new file mode 100644 index 00000000..8fa0f108 --- /dev/null +++ b/src/cbcm-v1.1beta1.json @@ -0,0 +1,393 @@ +{ + "auth": { + "oauth2": { + "scopes": { + "https://www.googleapis.com/auth/admin.directory.device.chromebrowsers": { + "description": "View and manage your Chrome browsers registered with Cloud Management" + }, + "https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly": { + "description": "View your Chrome browsers registered with Cloud Management" + } + } + } + }, + "basePath": "", + "baseUrl": "https://www.googleapis.com/admin/directory/v1.1beta1/customer/", + "batchPath": "batch", + "canonicalName": "cbcm", + "discoveryVersion": "v1", + "documentationLink": "https://support.google.com/chrome/a/answer/9681204", + "fullyEncodeReservedExpansion": true, + "icons": { + "x16": "http://www.google.com/images/icons/product/search-16.gif", + "x32": "http://www.google.com/images/icons/product/search-32.gif" + }, + "id": "cbcm:v1.1beta1", + "kind": "discovery#restDescription", + "mtlsRootUrl": "https://admin.mtls.googleapis.com/", + "name": "cbcm", + "ownerDomain": "google.com", + "ownerName": "Jay Lee", + "packagePath": "cbcm", + "parameters": { + "$.xgafv": { + "description": "V1 error format.", + "enum": [ + "1", + "2" + ], + "enumDescriptions": [ + "v1 error format", + "v2 error format" + ], + "location": "query", + "type": "string" + }, + "access_token": { + "description": "OAuth access token.", + "location": "query", + "type": "string" + }, + "alt": { + "default": "json", + "description": "Data format for response.", + "enum": [ + "json", + "media", + "proto" + ], + "enumDescriptions": [ + "Responses with Content-Type of application/json", + "Media download with context-dependent Content-Type", + "Responses with Content-Type of application/x-protobuf" + ], + "location": "query", + "type": "string" + }, + "callback": { + "description": "JSONP", + "location": "query", + "type": "string" + }, + "fields": { + "description": "Selector specifying which fields to include in a partial response.", + "location": "query", + "type": "string" + }, + "key": { + "description": "API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.", + "location": "query", + "type": "string" + }, + "oauth_token": { + "description": "OAuth 2.0 token for the current user.", + "location": "query", + "type": "string" + }, + "prettyPrint": { + "default": "true", + "description": "Returns response with indentations and line breaks.", + "location": "query", + "type": "boolean" + }, + "quotaUser": { + "description": "Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters.", + "location": "query", + "type": "string" + }, + "uploadType": { + "description": "Legacy upload protocol for media (e.g. \"media\", \"multipart\").", + "location": "query", + "type": "string" + }, + "upload_protocol": { + "description": "Upload protocol for media (e.g. \"raw\", \"multipart\").", + "location": "query", + "type": "string" + } + }, + "protocol": "rest", + "resources": { + "chromebrowsers": { + "methods": { + "delete": { + "description": "Deletes a browser.", + "flatPath": "{customer}/devices/chromebrowsers/{deviceId}", + "httpMethod": "DELETE", + "id": "cbcm.chromebrowsers.delete", + "parameterOrder": [ + "customer", + "deviceId" + ], + "parameters": { + "customer": { + "description": "Immutable ID of the G Suite account.", + "location": "path", + "required": true, + "type": "string" + }, + "deviceId": { + "description": "Immutable ID of the browser.", + "location": "path", + "required": true, + "type": "string" + } + }, + "path": "{customer}/devices/chromebrowsers/{deviceId}", + "scopes": [ + "https://www.googleapis.com/auth/admin.directory.device.chromebrowsers" + ] + }, + "get": { + "description": "Retrieves a browser.", + "flatPath": "{customer}/devices/chromebrowsers/{deviceId}", + "httpMethod": "GET", + "id": "cbcm.chromebrowsers.get", + "parameterOrder": [ + "customer", + "deviceId" + ], + "parameters": { + "customer": { + "description": "Immutable ID of the G Suite account.", + "location": "path", + "required": true, + "type": "string" + }, + "deviceId": { + "description": "Immutable ID of the browser.", + "location": "path", + "required": true, + "type": "string" + }, + "projection": { + "description": "Restrict information returned to a set of selected fields. FULL or BASIC.", + "location": "query", + "type": "string" + } + }, + "path": "{customer}/devices/chromebrowsers/{deviceId}", + "response": { + "$ref": "ChromeBrowser" + }, + "scopes": [ + "https://www.googleapis.com/auth/admin.directory.device.chromebrowsers", + "https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly" + ] + }, + "list": { + "description": "Retrieves a paginated list of all the browsers in a domain.", + "flatPath": "{customer}/devices/chromebrowsers", + "httpMethod": "GET", + "id": "cbcm.chromebrowsers.list", + "parameterOrder": [ + "customer" + ], + "parameters": { + "customer": { + "description": "Immutable ID of the G Suite account.", + "location": "path", + "required": true, + "type": "string" + }, + "maxResults": { + "description": "Maximum number of results to return.", + "format": "int32", + "location": "query", + "maximum": "100", + "minimum": "1", + "type": "integer" + }, + "orderBy": { + "description": "property to use for sorting results.", + "location": "query", + "type": "string" + }, + "orgUnitPath": { + "description": "The full path of the organizational unit or its unique ID.", + "location": "query", + "type": "string" + }, + "pageToken": { + "description": "Token to specify the next page in the list.", + "location": "query", + "type": "string" + }, + "projection": { + "description": "Restrict information returned to a set of selected fields. FULL or BASIC.", + "location": "query", + "type": "string" + }, + "query": { + "description": "Search string using the list page query language.", + "location": "query", + "type": "string" + }, + "sortOrder": { + "description": "Whether to return results in ascending or descending order. Must be used with the orderBy parameter.", + "location": "query", + "type": "string" + } + }, + "path": "{customer}/devices/chromebrowsers", + "response": { + "$ref": "ChromeBrowsers" + }, + "scopes": [ + "https://www.googleapis.com/auth/admin.directory.device.chromebrowsers", + "https://www.googleapis.com/auth/admin.directory.device.chromebrowsers.readonly" + ] + }, + "moveChromeBrowsersToOu": { + "description": "Move Chrome Browsers Device between Organization Units", + "flatPath": "{customer}/devices/chromebrowsers/moveChromeBrowsersToOu", + "httpMethod": "POST", + "id": "cbcm.chromebrowsers.moveChromeBrowsersToOu", + "parameterOrder": [ + "customer" + ], + "parameters": { + "customer": { + "description": "Immutable ID of the G Suite account.", + "location": "path", + "required": true, + "type": "string" + } + }, + "path": "{customer}/devices/chromebrowsers/moveChromeBrowsersToOu", + "request": { + "$ref": "MoveChromeBrowsersRequest" + }, + "scopes": [ + "https://www.googleapis.com/auth/admin.directory.device.chromebrowsers" + ] + }, + "update": { + "description": "Updates a browser.", + "flatPath": "{customer}/devices/chromebrowsers/{deviceId}", + "httpMethod": "PUT", + "id": "cbcm.chromebrowsers.update", + "parameterOrder": [ + "customer", + "deviceId" + ], + "parameters": { + "customer": { + "description": "Immutable ID of the G Suite account.", + "location": "path", + "required": true, + "type": "string" + }, + "deviceId": { + "description": "Immutable ID of the browser.", + "location": "path", + "required": true, + "type": "string" + }, + "projection": { + "description": "BASIC or FULL", + "location": "query", + "type": "string" + } + }, + "path": "{customer}/devices/chromebrowsers/{deviceId}", + "request": { + "$ref": "ChromeBrowser" + }, + "response": { + "$ref": "ChromeBrowser" + }, + "scopes": [ + "https://www.googleapis.com/auth/admin.directory.device.chromebrowsers" + ] + } + } + } + }, + "revision": "20201203", + "rootUrl": "https://www.googleapis.com/admin/directory/v1.1beta1/customer/", + "schemas": { + "ChromeBrowser": { + "id": "ChromeBrowser", + "properties": { + "annotatedAssetId": { + "description": "Asset identifier as annotated by the administrator or specified during enrollment.", + "type": "string" + }, + "annotatedLocation": { + "description": "Address or location of the device as annotated by the administrator.", + "type": "string" + }, + "annotatedNotes": { + "description": "Notes about this device as annotated by the administrator", + "type": "string" + }, + "annotatedUser": { + "description": "User of the device as annotated by the administrator.", + "type": "string" + }, + "deviceId": { + "annotations": { + "required": [ + "cbcm.chromebrowsers.update" + ] + }, + "description": "The unique ID of the device.", + "type": "string" + } + }, + "type": "object" + }, + "ChromeBrowsers": { + "id": "ChromeBrowsers", + "properties": { + "browsers": { + "description": "List of Chrome browser objects.", + "items": { + "$ref": "ChromeBrowser" + }, + "type": "array" + }, + "etag": { + "description": "ETag of the resource.", + "type": "string" + }, + "kind": { + "default": "admin#directory#chromeosdevices", + "description": "Kind of resource this is.", + "type": "string" + }, + "nextPageToken": { + "description": "Token used to access the next page of this result. To access the next page, use this token's value in the `pageToken` query string of this request.", + "type": "string" + } + }, + "type": "object" + }, + "MoveChromeBrowsersRequest": { + "properties": { + "org_unit_path": { + "annotations": { + "required": [ + "cbcm.chromebrowsers.moveChromeBrowsersToOu" + ] + }, + "description": "Destination organization unit to move devices to. Full path of the organizational unit or its ID prefixed with id:", + "type": "string" + }, + "resource_ids": { + "annotations": { + "required": [ + "cbcm.chromebrowsers.moveChromeBrowsersToOu" + ] + }, + "description": "List of unique device IDs of Chrome Browser Devices to move. A maximum of 600 browsers may be moved per request.", + "type": "array" + } + } + } + }, + "servicePath": "", + "title": "Admin SDK API", + "version": "cbcm_v1.1beta1" +} diff --git a/src/gam.spec b/src/gam.spec index f973da94..bc181f9e 100644 --- a/src/gam.spec +++ b/src/gam.spec @@ -12,6 +12,7 @@ proot = os.path.dirname(importlib.import_module('httplib2').__file__) extra_files = [(os.path.join(proot, 'cacerts.txt'), 'httplib2')] extra_files += copy_metadata('google-api-python-client') +extra_files = [('cbcm-v1.1beta1.json', 'cbcm-v1.1beta1.json')] a = Analysis(['gam/__main__.py'], hiddenimports=[], diff --git a/src/gam/__init__.py b/src/gam/__init__.py index 8a96ba47..430e8257 100755 --- a/src/gam/__init__.py +++ b/src/gam/__init__.py @@ -1,5 +1,6 @@ """Main behavioral methods and argument routing for GAM.""" + import base64 import configparser import csv @@ -49,6 +50,7 @@ from gam import display from gam import fileutils from gam.gapi import calendar as gapi_calendar from gam.gapi import cloudidentity as gapi_cloudidentity +from gam.gapi import cbcm as gapi_cbcm from gam.gapi.cloudidentity import devices as gapi_cloudidentity_devices from gam.gapi.cloudidentity import groups as gapi_cloudidentity_groups from gam.gapi.directory import asps as gapi_directory_asps @@ -10112,6 +10114,11 @@ def doRequestOAuth(login_hint=None, scopes=None): OAUTH2_SCOPES = [ + { + 'name': 'Chrome Browser Cloud Management', + 'subscopes': ['readonly'], + 'scopes': 'https://www.googleapis.com/auth/admin.directory.device.chromebrowsers', + }, { 'name': 'Classroom API - counts as 5 scopes', @@ -11077,6 +11084,10 @@ def ProcessGAMCommand(args): gapi_directory_resource.updateFeature() elif argument in ['adminrole']: gapi_directory_roles.update() + elif argument == 'deviceuserstate': + gapi_cloudidentity_devices.update_state() + elif argument in ['browser', 'browsers']: + gapi_cbcm.update() else: controlflow.invalid_argument_exit(argument, 'gam update') sys.exit(0) @@ -11135,6 +11146,8 @@ def ProcessGAMCommand(args): gapi_directory_resource.getBuildingInfo() elif argument in ['device']: gapi_cloudidentity_devices.info() + elif argument in ['browser', 'browsers']: + gapi_cbcm.info() else: controlflow.invalid_argument_exit(argument, 'gam info') sys.exit(0) @@ -11287,6 +11300,8 @@ def ProcessGAMCommand(args): doPrintShowAlerts() elif argument in ['alertfeedback', 'alertsfeedback']: doPrintShowAlertFeedback() + elif argument in ['browser', 'browsers']: + gapi_cbcm.print_() else: controlflow.invalid_argument_exit(argument, 'gam print') sys.exit(0) @@ -11305,6 +11320,11 @@ def ProcessGAMCommand(args): else: controlflow.invalid_argument_exit(argument, 'gam show') sys.exit(0) + elif command == 'move': + argument = sys.argv[2].lower() + if argument in ['browser', 'browsers']: + gapi_cbcm.move() + sys.exit(0) elif command in ['oauth', 'oauth2']: argument = sys.argv[2].lower() if argument in ['request', 'create']: diff --git a/src/gam/gapi/cbcm.py b/src/gam/gapi/cbcm.py new file mode 100644 index 00000000..cbdc6112 --- /dev/null +++ b/src/gam/gapi/cbcm.py @@ -0,0 +1,179 @@ +"""Chrome Browser Cloud Management API calls""" + +import csv +import os.path +import sys + +import gam +from gam.var import * +from gam import controlflow +from gam import display +from gam import fileutils +from gam import gapi +from gam.gapi.directory import orgunits as gapi_directory_orgunits +from gam import utils + + +def build(): + return gam.buildGAPIObject('cbcm') + + +def info(): + cbcm = build() + device_id = sys.argv[3] + projection = 'BASIC' + fields = None + i = 4 + while i < len(sys.argv): + myarg = sys.argv[i].lower().replace('_', '') + if myarg in ['basic', 'full']: + projection = myarg.upper() + i += 1 + elif myarg == 'fields': + fields = sys.argv[i+1] + i += 2 + else: + controlflow.invalid_argument_exit(sys.argv[i], 'gam info browser') + browser = gapi.call(cbcm.chromebrowsers(), 'get', + customer=GC_Values[GC_CUSTOMER_ID], + fields=fields, deviceId=device_id, + projection=projection) + display.print_json(browser) + + +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 update browsers') + if 'org_unit_path' not in body: + controlflow.missing_argument_exit('ou', 'gam update browsers') + elif not resource_ids: + controlflow.missing_argument_exit('query or ids', + 'gam update 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 print_(): + cbcm = build() + projection = 'BASIC' + query = None + fields = None + titles = [] + csv_rows = [] + todrive = False + sort_headers = False + i = 3 + while i < len(sys.argv): + myarg = sys.argv[i].lower().replace('_', '') + if myarg == 'query': + query = sys.argv[i+1] + i += 2 + elif myarg == 'projection': + projection = sys.argv[i + 1].upper() + i += 2 + elif myarg == 'todrive': + todrive = True + i += 1 + elif myarg == 'sortheaders': + sort_headers = True + i += 1 + elif myarg == 'fields': + fields = sys.argv[i + 1] + i += 2 + else: + controlflow.invalid_argument_exit(sys.argv[i], + 'gam print browsers') + if fields: + fields = f'browsers({fields}),nextPageToken' + 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=projection, + fields=fields) + for browser in browsers: + browser = utils.flatten_json(browser) + for a_key in browser: + if a_key not in titles: + titles.append(a_key) + csv_rows.append(browser) + if sort_headers: + display.sort_csv_titles(['name',], titles) + display.write_csv_file(csv_rows, titles, 'Browsers', todrive) + +def update(): + cbcm = build() + device_id = sys.argv[3] + body = {'deviceId': device_id} + attributes = ['user', 'location', 'notes', 'assetid'] + i = 4 + while i < len(sys.argv): + myarg = sys.argv[i].lower().replace('_', '') + if myarg in attributes: + attribute = f'annotated{myarg.capitalize()}' + body[attribute] = sys.argv[i+1] + i += 2 + else: + controlflow.invalid_argument_exit(sys.argv[i], + 'gam print browsers') + result = gapi.call(cbcm.chromebrowsers(), 'update', deviceId=device_id, + customer=GC_Values[GC_CUSTOMER_ID], body=body, + projection='BASIC', fields="deviceId") + print(f'Updated browser {result["deviceId"]}') diff --git a/src/gam/gapi/cloudidentity/devices.py b/src/gam/gapi/cloudidentity/devices.py index 56727eb8..0d1c23e4 100644 --- a/src/gam/gapi/cloudidentity/devices.py +++ b/src/gam/gapi/cloudidentity/devices.py @@ -3,6 +3,7 @@ import sys import googleapiclient +import gam from gam.var import * from gam import controlflow from gam import display @@ -11,7 +12,7 @@ from gam import gapi from gam import utils from gam.gapi import errors as gapi_errors from gam.gapi import cloudidentity as gapi_cloudidentity - +from gam.gapi.directory import customer as gapi_directory_customer def _get_device_customerid(): customer = GC_Values[GC_CUSTOMER_ID] @@ -49,6 +50,7 @@ def create(): result = gapi.call(ci.devices(), 'create', customer=customer, body=body) print(f'Created device {result["response"]["name"]}') + def _get_device_name(): name = sys.argv[3] if name == 'id': @@ -65,9 +67,15 @@ def info(): device = gapi.call(ci.devices(), 'get', name=name, customer=customer) device_users = gapi.get_all_pages(ci.devices().deviceUsers(), 'list', 'deviceUsers', parent=name, customer=customer) + for device_user in device_users: + parent = device_user['name'] + client_states = gapi.get_all_pages(ci.devices().deviceUsers().clientStates(), 'list', 'clientStates', parent=parent, customer=customer) display.print_json(device) print('Device Users:') display.print_json(device_users) + print('Client States:') + display.print_json(client_states) + def _generic_action(action, device_user=False): ci = gapi_cloudidentity.build_dwd() @@ -87,30 +95,126 @@ def _generic_action(action, device_user=False): op = gapi.call(endpoint, action, name=name, **kwargs) print(op) + def delete(): _generic_action('delete') + def cancel_wipe(): _generic_action('cancelWipe') + def wipe(): _generic_action('wipe') + def approve_user(): _generic_action('approve', True) + def block_user(): _generic_action('block', True) + def cancel_wipe_user(): _generic_action('cancelWipe', True) + def delete_user(): _generic_action('delete', True) + def wipe_user(): _generic_action('wipe', True) + +def update_state(): + ci = gapi_cloudidentity.build_dwd() + gapi_directory_customer.setTrueCustomerId() + customer = _get_device_customerid()[10:] + client_id = f'{customer}-gam' + body = {} + i = 3 + while i < len(sys.argv): + myarg = sys.argv[i].lower().replace('_', '') + if myarg == 'id': + deviceuser = sys.argv[i+1] + i += 2 + elif myarg == 'clientid': + client_id = f'{customer}-{sys.argv[i+1]}' + i += 2 + elif myarg in ['assettag', 'assettags']: + body['assetTags'] = sys.argv[i+1].split(',') + if body['assetTags'] == ['clear']: + # TODO: this doesn't work to clear + # existing values. Figure out why. + body['assetTags'] = [None] + i += 2 + elif myarg in ['compliantstate', 'compliancestate']: + comp_states = gapi.get_enum_values_minus_unspecified( + ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1ClientState']['properties']['complianceState']['enum']) + body['complianceState'] = sys.argv[i+1].upper() + if body['complianceState'] not in comp_states: + controlflow.expected_argument_exit('compliant_state', + ', '.join(comp_states), + sys.argv[i+1]) + i += 2 + elif myarg == 'customid': + body['customId'] = sys.argv[i+1] + i += 2 + elif myarg == 'healthscore': + health_scores = gapi.get_enum_values_minus_unspecified( + ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1ClientState']['properties']['healthScore']['enum']) + body['healthScore'] = sys.argv[i+1].upper() + if body['healthScore'] == 'CLEAR': + body['healthScore'] = None + if body['healthScore'] and body['healthScore'] not in health_scores: + controlflow.expected_argument_exit('health_score', + ', '.join(health_scores), + sys.argv[i+1]) + i += 2 + elif myarg == 'customvalue': + allowed_types = ['boolValue', 'numberValue', 'stringValue'] + value_type = f'{sys.argv[i+1].lower()}Value' + if value_type not in allowed_types: + controlflow.expected_argument_exit('custom_value', + ', '.join(allowed_types), + sys.argv[i+1]) + key = sys.argv[i+2] + value = sys.argv[i+3] + if value_type == 'boolValue': + value = gam.getBoolean(value, key) + elif value_type == 'numberValue': + value = int(value) + if 'keyValuePairs' not in body: + body['keyValuePairs'] = {} + body['keyValuePairs'][key] = {value_type: value} + i += 4 + elif myarg in ['managedstate']: + managed_states = gapi.get_enum_values_minus_unspecified( + ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1ClientState']['properties']['managed']['enum']) + body['managed'] = sys.argv[i+1].upper() + if body['managed'] == 'CLEAR': + body['managed'] = None + if body['managed'] and body['managed'] not in managed_states: + controlflow.expected_argument_exit('managed_state', + ', '.join(managed_states), + sys.argv[i+1]) + i += 2 + elif myarg in ['scorereason']: + body['scoreReason'] = sys.argv[i+1] + if body['scoreReason'] == 'clear': + body['scoreReason'] = None + i += 2 + else: + controlflow.invalid_argument_exit(sys.argv[i], 'gam update deviceuserstate') + name = f'{deviceuser}/clientStates/{client_id}' + updateMask = ','.join(body.keys()) + result = gapi.call(ci.devices().deviceUsers().clientStates(), 'patch', + name=name, customer=f'customers/{customer}', updateMask=updateMask, body=body) + display.print_json(result) + + def print_(): ci = gapi_cloudidentity.build_dwd() customer = _get_device_customerid() diff --git a/src/gam/gapi/directory/mobiledevices.py b/src/gam/gapi/directory/mobiledevices.py index 84d87e43..65291b11 100644 --- a/src/gam/gapi/directory/mobiledevices.py +++ b/src/gam/gapi/directory/mobiledevices.py @@ -27,13 +27,13 @@ def info(): 'get', customerId=GC_Values[GC_CUSTOMER_ID], resourceId=resourceId) - if 'deviceId' in info: + if 'deviceId' in device_info: device_info['deviceId'] = device_info['deviceId'].encode('unicode-escape').decode( UTF8) attrib = 'securityPatchLevel' - if attrib in info and int(device_info[attrib]): + if attrib in device_info and int(device_info[attrib]): device_info[attrib] = utils.formatTimestampYMDHMS(device_info[attrib]) - display.print_json(info) + display.print_json(device_info) diff --git a/src/gam/var.py b/src/gam/var.py index 42c69630..3c54a3dd 100644 --- a/src/gam/var.py +++ b/src/gam/var.py @@ -8,7 +8,7 @@ import platform import re GAM_AUTHOR = 'Jay Lee ' -GAM_VERSION = '5.24' +GAM_VERSION = '5.30' GAM_LICENSE = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' GAM_URL = 'https://git.io/gam' @@ -262,6 +262,7 @@ API_VER_MAPPING = { 'alertcenter': 'v1beta1', 'appsactivity': 'v1', 'calendar': 'v3', + 'cbcm': 'v1.1beta1', 'classroom': 'v1', 'cloudidentity': 'v1', 'cloudidentity_beta': 'v1beta1', @@ -297,7 +298,7 @@ API_SCOPE_MAPPING = { 'https://www.googleapis.com/auth/drive', ], 'calendar': ['https://www.googleapis.com/auth/calendar',], - 'cloudidentity': ['https://www.googleapis.com/auth/cloud-identity',], + 'cloudidentity': ['https://www.googleapis.com/auth/cloud-identity', 'https://www.googleapis.com/auth/cloud-identity.devices.lookup'], 'drive': ['https://www.googleapis.com/auth/drive',], 'drive3': ['https://www.googleapis.com/auth/drive',], 'gmail': [