From ce4f74bc612bb3261d33bdecea68f78b83015458 Mon Sep 17 00:00:00 2001 From: Jay Lee Date: Thu, 11 Mar 2021 17:07:13 -0500 Subject: [PATCH] New User Invitation API --- src/gam/__init__.py | 21 +++ src/gam/gapi/cloudidentity/userinvitations.py | 150 ++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 src/gam/gapi/cloudidentity/userinvitations.py diff --git a/src/gam/__init__.py b/src/gam/__init__.py index 114ace7f..12522a21 100755 --- a/src/gam/__init__.py +++ b/src/gam/__init__.py @@ -55,6 +55,7 @@ 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.cloudidentity import userinvitations as gapi_cloudidentity_userinvitations from gam.gapi import contactdelegation as gapi_contactdelegation from gam.gapi.directory import asps as gapi_directory_asps from gam.gapi.directory import cros as gapi_directory_cros @@ -10262,6 +10263,11 @@ OAUTH2_SCOPES = [ 'subscopes': ['readonly'], 'scopes': 'https://www.googleapis.com/auth/cloud-identity.groups' }, + { + 'name': 'Cloud Identity - User Invitations', + 'subscopes': ['readonly'], + 'scopes': 'https://www.googleapis.com/auth/cloud-identity.userinvitations' + }, { 'name': 'Contact Delegation', 'subscopes': ['readonly'], @@ -11168,6 +11174,8 @@ def ProcessGAMCommand(args): gapi_directory_roles.create() elif argument in ['browsertoken', 'browsertokens']: gapi_cbcm.createtoken() + elif argument in ['userinvitation', 'userinvitations']: + gapi_cloudidentity_userinvitations.send() else: controlflow.invalid_argument_exit(argument, 'gam create') sys.exit(0) @@ -11288,6 +11296,8 @@ def ProcessGAMCommand(args): gapi_cloudidentity_devices.info_state() elif argument in ['browser', 'browsers']: gapi_cbcm.info() + elif argument in ['userinvitation', 'userinvitations']: + gapi_cloudidentity_userinvitations.get() else: controlflow.invalid_argument_exit(argument, 'gam info') sys.exit(0) @@ -11295,6 +11305,8 @@ def ProcessGAMCommand(args): argument = sys.argv[2].lower() if argument in ['guardianinvitation', 'guardianinvitations']: doCancelGuardianInvitation() + elif argument in ['userinvitation', 'userinvitations']: + gapi_cloudidentity_userinvitations.cancel() else: controlflow.invalid_argument_exit(argument, 'gam cancel') sys.exit(0) @@ -11453,6 +11465,8 @@ def ProcessGAMCommand(args): gapi_cbcm.printshowtokens(True) elif argument in ['vaultcount']: gapi_vault.print_count() + elif argument in ['userinvitations']: + gapi_cloudidentity_userinvitations.print_() else: controlflow.invalid_argument_exit(argument, 'gam print') sys.exit(0) @@ -11560,6 +11574,11 @@ def ProcessGAMCommand(args): else: controlflow.invalid_argument_exit(argument, 'gam rotate') sys.exit(0) + elif command == 'check': + argument = sys.argv[2].lower() + if argument in ['userinvitation', 'userinvitations']: + gapi_cloudidentity_userinvitations.is_invitable_user() + sys.exit(0) elif command in ['cancelwipe', 'wipe', 'approve', 'block', 'sync']: target = sys.argv[2].lower().replace('_', '') if target in ['device', 'devices']: @@ -11889,6 +11908,8 @@ def ProcessGAMCommand(args): checkWhat = sys.argv[4].replace('_', '').lower() if checkWhat == 'serviceaccount': doCheckServiceAccount(users) + elif checkWhat == 'isinvitable': + gapi_cloudidentity_userinvitations.bulk_is_invitable(users) else: controlflow.invalid_argument_exit(checkWhat, 'gam check') diff --git a/src/gam/gapi/cloudidentity/userinvitations.py b/src/gam/gapi/cloudidentity/userinvitations.py new file mode 100644 index 00000000..fe176d1f --- /dev/null +++ b/src/gam/gapi/cloudidentity/userinvitations.py @@ -0,0 +1,150 @@ +"""Methods related to Cloud Identity User Invitation API""" +# pylint: disable=unused-wildcard-import wildcard-import +import sys + +import googleapiclient + +from gam.var import * +from gam import controlflow +from gam import display +from gam import gapi +from gam.gapi import errors as gapi_errors +from gam.gapi import cloudidentity as gapi_cloudidentity + +def _get_customerid(): + ''' returns customer in format needed for this API''' + customer = GC_Values[GC_CUSTOMER_ID] + if customer.startswith('C'): + customer = customer[1:] + return f'customers/{customer}' + +def _reduce_name(name): + ''' converts long name into email address''' + return name.split('/')[-1] + +def _generic_action(action): + '''generic function to call actionable APIs''' + svc = gapi_cloudidentity.build('cloudidentity_beta') + customer = _get_customerid() + email = sys.argv[3].lower() + name = f'{customer}/userinvitations/{email}' + action_map = { + 'cancel': 'Cancelling', + 'send': 'Sending' + } + print_action = action_map[action] + kwargs = {} + if action != 'get': + kwargs['body'] = {} + print(f'{print_action} user invite...') + result = gapi.call(svc.customers().userinvitations(), action, + name=name, **kwargs) + name = result.get('response', {}).get('name') + if name: + result['response']['name'] = _reduce_name(name) + display.print_json(result) + +def _generic_get(get_type): + '''generic function to call read data APIs''' + svc = gapi_cloudidentity.build('cloudidentity_beta') + customer = _get_customerid() + email = sys.argv[3].lower() + name = f'{customer}/userinvitations/{email}' + result = gapi.call(svc.customers().userinvitations(), get_type, + name=name) + if 'name' in result: + result['name'] = _reduce_name(result['name']) + display.print_json(result) + + +# /batch is broken for Cloud Identity. Once fixed move this to using batch. +# Current serial implementation will be SLOW... +def bulk_is_invitable(emails): + '''gam check isinvitable''' + def _invite_result(request_id, response, _): + if response.get('isInvitableUser'): + rows.append({'invitableUsers': request_id}) + + svc = gapi_cloudidentity.build('cloudidentity_beta') + customer = _get_customerid() + todrive = False + #batch_size = 1000 + #ebatch = svc.new_batch_http_request(callback=_invite_result) + rows = [] + throw_reasons = [gapi_errors.ErrorReason.FOUR_O_THREE] + for email in emails: + name = f'{customer}/userinvitations/{email}' + endpoint = svc.customers().userinvitations() + #if len(ebatch._order) == batch_size: + # ebatch.execute() + # ebatch = svc.new_batch_http_request(callback=_invite_result) + #req = endpoint.isInvitableUser(name=name) + #ebatch.add(req, request_id=email) + try: + result = gapi.call(endpoint, + 'isInvitableUser', + throw_reasons=throw_reasons, + name=name) + except googleapiclient.errors.HttpError: + continue + if result.get('isInvitableUser'): + rows.append({'invitableUsers': email}) + #ebatch.execute() + titles = ['invitableUsers'] + display.write_csv_file(rows, titles, 'Invitable Users', todrive) + + +def cancel(): + '''gam cancel userinvitation ''' + _generic_action('cancel') + + +def get(): + '''gam info userinvitation ''' + _generic_get('get') + + +def is_invitable_user(): + '''gam check userinvitation ''' + _generic_get('isInvitableUser') + + +def send(): + '''gam create userinvitation ''' + _generic_action('send') + + +def print_(): + '''gam print userinvitations''' + svc = gapi_cloudidentity.build('cloudidentity_beta') + customer = _get_customerid() + todrive = False + titles = [] + rows = [] + filter_ = None + i = 3 + while i < len(sys.argv): + myarg = sys.argv[i].lower().replace('_', '') + if myarg == 'filter': + filter_ = sys.argv[i+1] + i += 2 + elif myarg == 'todrive': + todrive = True + i += 1 + else: + controlflow.invalid_argument_exit(sys.argv[i], + 'gam print userinvitations') + invites = gapi.get_all_pages(svc.customers().userinvitations(), + 'list', + 'userInvitations', + parent=customer, + filter=filter_) + for invite in invites: + invite['name'] = _reduce_name(invite['name']) + row = {} + for key, val in invite.items(): + if key not in titles: + titles.append(key) + row[key] = val + rows.append(row) + display.write_csv_file(rows, titles, 'User Invitations', todrive)