Cloud Identity Devices API, 5.20

This commit is contained in:
Jay Lee
2020-09-16 15:33:55 -04:00
parent 4974150357
commit c2f53577ab
5 changed files with 354 additions and 16 deletions

View File

@@ -52,6 +52,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.cloudidentity import devices as gapi_cloudidentity_devices
from gam.gapi.cloudidentity import groups as gapi_cloudidentity_groups
from gam.gapi import directory as gapi_directory
from gam.gapi.directory import asps as gapi_directory_asps
@@ -807,12 +808,7 @@ def getSvcAcctCredentials(scopes, act_as):
def getAPIVersion(api):
version = API_VER_MAPPING.get(api, 'v1')
if api in ['directory', 'reports', 'datatransfer']:
api = 'admin'
elif api == 'drive3':
api = 'drive'
elif api == 'cloudresourcemanagerv1':
api = 'cloudresourcemanager'
api = API_NAME_MAPPING.get(api, api)
return (api, version, f'{api}-{version}')
@@ -11177,6 +11173,8 @@ def ProcessGAMCommand(args):
doCreateDataTransfer()
elif argument == 'domain':
gapi_directory_domains.create()
elif argument == 'device':
gapi_cloudidentity_devices.create()
elif argument in ['domainalias', 'aliasdomain']:
gapi_directory_domainaliases.create()
elif argument == 'admin':
@@ -11315,6 +11313,8 @@ def ProcessGAMCommand(args):
gapi_vault.getExportInfo()
elif argument in ['building']:
gapi_directory_resource.getBuildingInfo()
elif argument in ['device']:
gapi_cloudidentity_devices.info()
else:
controlflow.invalid_argument_exit(argument, 'gam info')
sys.exit(0)
@@ -11331,6 +11331,10 @@ def ProcessGAMCommand(args):
doDeleteUser()
elif argument == 'group':
gapi_directory_groups.delete()
elif argument == 'device':
gapi_cloudidentity_devices.delete()
elif argument == 'deviceuser':
gapi_cloudidentity_devices.delete_user()
elif argument == 'cigroup':
gapi_cloudidentity_groups.delete()
elif argument in ['nickname', 'alias']:
@@ -11405,8 +11409,12 @@ def ProcessGAMCommand(args):
gapi_directory_groups.print_()
elif argument == 'cigroups':
gapi_cloudidentity_groups.print_()
elif argument == 'devices':
gapi_cloudidentity_devices.print_()
elif argument in ['groupmembers', 'groupsmembers']:
gapi_directory_groups.print_members()
elif argument == 'devices':
gapi_cloudidentity_devices.print_()
elif argument in ['cigroupmembers', 'cigroupsmembers']:
gapi_cloudidentity_groups.print_members()
elif argument in ['orgs', 'ous']:
@@ -11561,6 +11569,25 @@ def ProcessGAMCommand(args):
else:
controlflow.invalid_argument_exit(argument, 'gam rotate')
sys.exit(0)
elif command in ['cancelwipe', 'wipe', 'approve', 'block', 'sync']:
target = sys.argv[2].lower().replace('_', '')
if target in ['device', 'devices']:
if command == 'cancelwipe':
gapi_cloudidentity_devices.cancel_wipe()
elif command == 'wipe':
gapi_cloudidentity_devices.wipe()
elif command == 'sync':
gapi_cloudidentity_devices.sync()
elif target == 'deviceuser':
if command == 'cancelwipe':
gapi_cloudidentity_devices.cancel_wipe_user()
elif command == 'wipe':
gapi_cloudidentity_devices.wipe_user()
elif command == 'approve':
gapi_cloudidentity_devices.approve_user()
elif command == 'block':
gapi_cloudidentity_devices.block_user()
sys.exit(0)
users = getUsersToModify()
command = sys.argv[3].lower()
if command == 'print' and len(sys.argv) == 4:

View File

@@ -1,5 +1,9 @@
import gam
def build():
return gam.buildGAPIObject('cloudidentity')
def build(api='cloudidentity'):
return gam.buildGAPIObject(api)
def build_dwd(api='cloudidentity'):
admin = gam._getValueFromOAuth('email')
return gam.buildGAPIServiceObject(api, admin, True)

View File

@@ -0,0 +1,296 @@
import csv
import sys
import googleapiclient
import gam
from gam.var import *
from gam import controlflow
from gam import display
from gam import fileutils
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
from gam.gapi.directory import groups as gapi_directory_groups
def create():
ci = gapi_cloudidentity.build_dwd()
customer = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
device_types = gapi.get_enum_values_minus_unspecified(
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1Device']['properties']['deviceType']['enum'])
body = {}
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg == 'serialnumber':
body['serialNumber'] = sys.argv[i+1]
i += 2
elif myarg == 'devicetype':
body['deviceType'] = sys.argv[i+1].upper()
if body['deviceType'] not in device_types:
controlflow.expected_argument_exit('device_type',
', '.join(device_types),
sys.argv[i+1])
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam create device')
if not body.get('serialNumber') or not body.get('deviceType'):
controlflow.system_error_exit(
3, 'serial_number and device_type are required arguments for "gam create device".')
result = gapi.call(ci.devices(), 'create', customer=customer, body=body)
print(f'Created device {result["response"]["name"]}')
def info():
ci = gapi_cloudidentity.build_dwd()
customer = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
name = sys.argv[3]
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)
print(device)
print(device_users)
def _generic_action(action, device_user=False):
ci = gapi_cloudidentity.build_dwd()
customer = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
# bah, inconsistencies in API
if action == 'delete':
kwargs = {'customer': customer}
else:
kwargs = {'body': {'customer': customer}}
if device_user:
endpoint = ci.devices().deviceUsers()
else:
endpoint = ci.devices()
name = None
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
# The API calls it "name" but GAM will expose as "id" to avoid admin confusion.
if myarg == 'id':
name = sys.argv[i+1]
if not name.startswith('devices/'):
name = f'devices/{name}'
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], f'gam {action} device')
if not name:
controlflow.system_error_exit(3, f'id is a required argument for "gam {action} device".')
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 print_():
ci = gapi_cloudidentity.build_dwd()
customer = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
parent = 'devices/-'
filter = None
get_device_users = True
get_device_views = ['COMPANY_INVENTORY', 'USER_ASSIGNED_DEVICES']
titles = []
csvRows = []
todrive = False
sortHeaders = False
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['filter', 'query']:
filter = sys.argv[i+1]
i += 2
elif myarg == 'nocompanydevices':
get_device_views.remove('COMPANY_INVENTORY')
i += 1
elif myarg == 'nopersonaldevices':
get_device_views.remove('USER_ASSIGNED_DEVICES')
i += 1
elif myarg == 'todrive':
todrive = True
i += 1
elif myarg == 'sortheaders':
sortHeaders = True
i += 1
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam print devices')
view_name_map = {
'COMPANY_INVENTORY': 'Company Devices',
'USER_ASSIGNED_DEVICES': 'Personal Devices',
}
devices = []
for view in get_device_views:
view_name = view_name_map.get(view, 'Devices')
page_message = gapi.got_total_items_msg(view_name, '...\n')
devices += gapi.get_all_pages(ci.devices(), 'list', 'devices',
customer=customer, page_message=page_message,
pageSize=100, filter=filter, view=view)
if get_device_users:
page_message = gapi.got_total_items_msg('Device Users', '...\n')
device_users = gapi.get_all_pages(ci.devices().deviceUsers(), 'list',
'deviceUsers', customer=customer, parent=parent,
page_message=page_message, pageSize=20, filter=filter)
for device_user in device_users:
for device in devices:
if device_user.get('name').startswith(device.get('name')):
if 'users' not in device:
device['users'] = []
device['users'].append(device_user)
break
for device in devices:
device = utils.flatten_json(device)
for a_key in device:
if a_key not in titles:
titles.append(a_key)
csvRows.append(device)
if sortHeaders:
display.sort_csv_titles([
'name',
], titles)
display.write_csv_file(csvRows, titles, 'Devices', todrive)
def sync():
ci = gapi_cloudidentity.build_dwd()
device_types = gapi.get_enum_values_minus_unspecified(
ci._rootDesc['schemas']['GoogleAppsCloudidentityDevicesV1Device']['properties']['deviceType']['enum'])
customer = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
filter = None
csv_file = None
serialnumber_column = 'serialNumber'
devicetype_column = 'deviceType'
static_devicetype = None
assetid_column = None
unassigned_missing_action = 'delete'
assigned_missing_action = 'donothing'
missing_actions = ['delete', 'wipe', 'donothing']
i = 3
while i < len(sys.argv):
myarg = sys.argv[i].lower().replace('_', '')
if myarg in ['filter', 'query']:
filter = sys.argv[i+1]
i += 2
elif myarg == 'csvfile':
csv_file = sys.argv[i+1]
i += 2
elif myarg == 'serialnumbercolumn':
serialnumber_column = sys.argv[i+1]
i += 2
elif myarg == 'devicetypecolumn':
devicetype_column = sys.argv[i+1]
i += 2
elif myarg == 'staticdevicetype':
static_devicetype = sys.argv[i+1].upper()
if static_devicetype not in device_types:
controlflow.expected_argument_exit('device_type',
', '.join(device_types),
sys.argv[i+1])
i += 2
elif myarg == 'assetidcolumn':
assetid_column = sys.argv[i+1]
i += 2
elif myarg == 'unassigned_missing_action':
unassigned_missing_action = sys.argv[i+1].lower().replace('_', '')
if unassigned_missing_action not in missing_actions:
controlflow.expected_argument_exit('unassigned_missing_action',
', '.join(missing_actions),
sys.argv[i+1])
i += 2
elif myarg == 'assigned_missing_action':
assigned_missing_action = sys.argv[i+1].lower().replace('_', '')
if assigned_missing_action not in missing_actions:
controlflow.expected_argument_exit('assigned_missing_action',
', '.join(missing_actions),
sys.argv[i+1])
i += 2
else:
controlflow.invalid_argument_exit(sys.argv[i], 'gam sync devices')
f = fileutils.open_file(csv_file)
input_file = csv.DictReader(f, restval='')
if serialnumber_column not in input_file.fieldnames:
controlflow.csv_field_error_exit(serialnumber_column, input_file.fieldnames)
if not static_devicetype and devicetype_column not in input_file.fieldnames:
controlflow.csv_field_error_exit(devicetype_column, input_file.fieldnames)
if assetid_column and assetid_column not in input_file.fieldnames:
controlflow.csv_field_error_exit(assetid_column, input_file.fieldnames)
local_devices = []
for row in input_file:
# upper() is very important to comparison since Google
# always return uppercase serials
serialnumber = row[serialnumber_column].strip().upper()
local_device = {'serialNumber': serialnumber}
if static_devicetype:
local_device['deviceType'] = static_devicetype
else:
local_device['deviceType'] = row[devicetype_column].strip()
if assetid_column:
local_device['assetTag'] = row[assetid_column].strip()
local_devices.append(local_device)
fileutils.close_file(f)
page_message = gapi.got_total_items_msg('Company Devices', '...\n')
device_fields = ['serialNumber', 'deviceType', 'lastSyncTime', 'name']
if assetid_column:
device_fields.append('assetTag')
fields = f'nextPageToken,devices({",".join(device_fields)})'
remote_devices = gapi.get_all_pages(ci.devices(), 'list', 'devices',
customer=customer, page_message=page_message,
pageSize=100, filter=filter, view='COMPANY_INVENTORY', fields=fields)
remote_device_map = {}
for remote_device in remote_devices:
sn = remote_device['serialNumber']
last_sync = remote_device.pop('lastSyncTime')
name = remote_device.pop('name')
remote_device_map[sn] = {'name': name}
if last_sync == '1970-01-01T00:00:00Z':
remote_device_map[sn]['unassigned'] = True
devices_to_add = [device for device in local_devices if device not in remote_devices]
missing_devices = [device for device in remote_devices if device not in local_devices]
print(f'Need to add {len(devices_to_add)} and remove {len(missing_devices)} devices...')
for add_device in devices_to_add:
print(f'Creating {add_device["serialNumber"]}')
try:
result = gapi.call(ci.devices(), 'create', customer=customer,
throw_reasons=[gapi_errors.ErrorReason.FOUR_O_NINE], body=add_device)
print(f' created {result["response"]["deviceType"]} device {result["response"]["name"]} with serial {result["response"]["serialNumber"]}')
except googleapiclient.errors.HttpError:
print(f' {add_device["serialNumber"]} already exists')
for missing_device in missing_devices:
sn = missing_device['serialNumber']
name = remote_device_map[sn]['name']
unassigned = remote_device_map[sn].get('unassigned')
action = unassigned_missing_action if unassigned else assigned_missing_action
if action == 'donothing':
pass
else:
if action == 'delete':
kwargs = {'customer': customer}
else:
kwargs = {'body': {'customer': customer}}
gapi.call(ci.devices(), unassigned_missing_action,
name=name, **kwargs)
print(f'{action}d {sn}')

View File

@@ -13,7 +13,7 @@ from gam.gapi.directory import groups as gapi_directory_groups
def create():
ci = gapi_cloudidentity.build()
ci = gapi_cloudidentity.build('cloudidentity_beta')
initialGroupConfig = 'EMPTY'
gapi_directory_customer.setTrueCustomerId()
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
@@ -65,7 +65,7 @@ def create():
def delete():
ci = gapi_cloudidentity.build()
ci = gapi_cloudidentity.build('cloudidentity_beta')
group = sys.argv[3]
name = group_email_to_id(ci, group)
print(f'Deleting group {group}')
@@ -73,7 +73,7 @@ def delete():
def info():
ci = gapi_cloudidentity.build()
ci = gapi_cloudidentity.build('cloudidentity_beta')
group = gam.normalizeEmailAddressOrUID(sys.argv[3])
getUsers = True
showJoinDate = True
@@ -128,7 +128,7 @@ def info():
def info_member():
ci = gapi_cloudidentity.build()
ci = gapi_cloudidentity.build('cloudidentity_beta')
member = gam.normalizeEmailAddressOrUID(sys.argv[3])
group = gam.normalizeEmailAddressOrUID(sys.argv[4])
group_name = gapi.call(ci.groups(),
@@ -158,7 +158,7 @@ GROUP_ROLES_MAP = {
def print_():
ci = gapi_cloudidentity.build()
ci = gapi_cloudidentity.build('cloudidentity_beta')
i = 3
members = membersCountOnly = managers = managersCountOnly = owners = ownersCountOnly = False
gapi_directory_customer.setTrueCustomerId()
@@ -313,7 +313,7 @@ def print_():
def print_members():
ci = gapi_cloudidentity.build()
ci = gapi_cloudidentity.build('cloudidentity_beta')
todrive = False
gapi_directory_customer.setTrueCustomerId()
parent = f'customers/{GC_Values[GC_CUSTOMER_ID]}'
@@ -429,7 +429,7 @@ def update():
]
return (role, users_email)
ci = gapi_cloudidentity.build()
ci = gapi_cloudidentity.build('cloudidentity_beta')
group = sys.argv[3]
myarg = sys.argv[4].lower()
items = []

View File

@@ -228,12 +228,22 @@ V1_DISCOVERY_APIS = {
'siteVerification',
}
API_NAME_MAPPING = {
'directory': 'admin',
'reports': 'admin',
'datatransfer': 'admin',
'drive3': 'drive',
'cloudresourcemanagerv1': 'cloudresourcemanager',
'cloudidentity_beta': 'cloudidentity',
}
API_VER_MAPPING = {
'alertcenter': 'v1beta1',
'appsactivity': 'v1',
'calendar': 'v3',
'classroom': 'v1',
'cloudidentity': 'v1beta1',
'cloudidentity': 'v1',
'cloudidentity_beta': 'v1beta1',
'cloudresourcemanager': 'v2',
'cloudresourcemanagerv1': 'v1',
'datatransfer': 'datatransfer_v1',
@@ -266,6 +276,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',],
'drive': ['https://www.googleapis.com/auth/drive',],
'drive3': ['https://www.googleapis.com/auth/drive',],
'gmail': [