diff --git a/src/GamCommands.txt b/src/GamCommands.txt index 00f30f86..c89ba096 100644 --- a/src/GamCommands.txt +++ b/src/GamCommands.txt @@ -1005,8 +1005,8 @@ gam delete org|ou gam info org|ou [nousers|notsuspended|suspended] [children|child] gam print orgs|ous [todrive] [toplevelonly] [from_parent ] [allfields|(fields )] -gam create alias|nickname user|group|target | -gam update alias|nickname user|group|target | +gam create alias|nickname user|group|target | [verifynotinvitable] +gam update alias|nickname user|group|target | [verifynotinvitable] gam delete alias|nickname [user|group|target] | gam info alias|nickname gam print aliases|nicknames [todrive] [shownoneditable] [nogroups] [nousers] [(query )|(queries )|(cigroup )] [roles ] -gam create group * -gam update group [email ] * +gam create group * [verifynotinvitable] +gam update group [email ] * [verifynotinvitable] gam update group add [owner|manager|member] [notsuspended|suspended] [allmail|daily|digest|none|nomail] gam update group delete|remove [owner|manager|member] gam update group sync [owner|manager|member] [notsuspended|suspended] [allmail|daily|digest|none|nomail] @@ -1388,8 +1388,8 @@ gam info schema gam show schema|schemas gam print schema|schemas -gam create user * -gam update user * [clearschema ] [clearschema .] +gam create user * [verifynotinvitable] +gam update user * [clearschema ] [clearschema .] [verifynotinvitable] gam delete user gam undelete user [org|ou ] gam info user [] [noaliases] [nogroups] [nolicenses|nolicences] [noschemas] [schemas|custom ] [userview] [skus|sku ] diff --git a/src/gam/__init__.py b/src/gam/__init__.py index f61958a5..44285253 100755 --- a/src/gam/__init__.py +++ b/src/gam/__init__.py @@ -6505,6 +6505,7 @@ def getUserAttributes(i, cd, updateCmd): need_password = True need_to_hash_password = True need_to_b64_decrypt_password = False + verifyNotInvitable = False while i < len(sys.argv): myarg = sys.argv[i].lower() if myarg in ['firstname', 'givenname']: @@ -7020,6 +7021,9 @@ def getUserAttributes(i, cd, updateCmd): else: body[up][schemaName][fieldName] = sys.argv[i] i += 1 + elif myarg == 'verifynotinvitable': + verifyNotInvitable = True + i += 1 else: controlflow.invalid_argument_exit( sys.argv[i], f"gam {['create', 'update'][updateCmd]} user") @@ -7034,7 +7038,7 @@ def getUserAttributes(i, cd, updateCmd): if body['password'].lower()[:5] in ['{md5}', '{sha}']: body['password'] = body['password'][5:] body['password'] = base64.b64decode(body['password']).hex() - return body + return (body, verifyNotInvitable) def getCRMService(login_hint): @@ -8197,7 +8201,10 @@ def extract_nested_zip(zippedFile, toFolder, spacing=' '): def doCreateUser(): cd = buildGAPIObject('directory') - body = getUserAttributes(3, cd, False) + body, verifyNotInvitable = getUserAttributes(3, cd, False) + if (verifyNotInvitable and + gapi_cloudidentity_userinvitations.is_invitable_user(body['primaryEmail'])): + controlflow.system_error_exit(51, f'User not created, {body["primaryEmail"]} is an unmanaged account') print(f'Creating account for {body["primaryEmail"]}') gapi.call(cd.users(), 'insert', body=body, fields='primaryEmail') @@ -8213,6 +8220,15 @@ def doCreateAlias(): controlflow.expected_argument_exit( 'target type', ', '.join(['user', 'group', 'target']), target_type) targetKey = normalizeEmailAddressOrUID(sys.argv[5]) + if len(sys.argv) > 6: + myarg = sys.argv[6].lower().replace('_', '') + if myarg != 'verifynotinvitable': + controlflow.system_error_exit( + 3, + f'{myarg} is not a valid argument for "gam create alias"' + ) + if gapi_cloudidentity_userinvitations.is_invitable_user(body['alias']): + controlflow.system_error_exit(51, f'Alias not created, {body["alias"]} is an unmanaged account') print(f'Creating alias {body["alias"]} for {target_type} {targetKey}') if target_type == 'user': gapi.call(cd.users().aliases(), 'insert', userKey=targetKey, body=body) @@ -8242,7 +8258,7 @@ def doUpdateUser(users, i): cd = buildGAPIObject('directory') if users is None: users = [normalizeEmailAddressOrUID(sys.argv[3])] - body = getUserAttributes(i, cd, True) + body, verifyNotInvitable = getUserAttributes(i, cd, True) vfe = 'primaryEmail' in body and body['primaryEmail'][:4].lower() == 'vfe@' for user in users: userKey = user @@ -8262,6 +8278,9 @@ def doUpdateUser(users, i): 'primary': False, 'address': user_primary }] + if (verifyNotInvitable and'primaryEmail' in body and + gapi_cloudidentity_userinvitations.is_invitable_user(body['primaryEmail'])): + controlflow.system_error_exit(51, f'User {user} not updated, new primaryEmail {body["primaryEmail"]} is an unmanaged account') sys.stdout.write(f'updating user {user}...\n') if body: gapi.call(cd.users(), 'update', userKey=userKey, body=body) @@ -8296,6 +8315,15 @@ def doUpdateAlias(): controlflow.expected_argument_exit( 'target type', ', '.join(['user', 'group', 'target']), target_type) target_email = normalizeEmailAddressOrUID(sys.argv[5]) + if len(sys.argv) > 6: + myarg = sys.argv[6].lower().replace('_', '') + if myarg != 'verifynotinvitable': + controlflow.system_error_exit( + 3, + f'{myarg} is not a valid argument for "gam update alias"' + ) + if gapi_cloudidentity_userinvitations.is_invitable_user(alias): + controlflow.system_error_exit(51, f'Alias not updated, {alias} is an unmanaged account') try: gapi.call(cd.users().aliases(), 'delete', @@ -11626,7 +11654,7 @@ def ProcessGAMCommand(args): elif command == 'check': argument = sys.argv[2].lower() if argument in ['isinvitable', 'userinvitation', 'userinvitations']: - gapi_cloudidentity_userinvitations.is_invitable_user() + gapi_cloudidentity_userinvitations.check() sys.exit(0) elif command in ['cancelwipe', 'wipe', 'approve', 'block', 'sync']: target = sys.argv[2].lower().replace('_', '') diff --git a/src/gam/gapi/cloudidentity/userinvitations.py b/src/gam/gapi/cloudidentity/userinvitations.py index d8d2d714..35d87521 100644 --- a/src/gam/gapi/cloudidentity/userinvitations.py +++ b/src/gam/gapi/cloudidentity/userinvitations.py @@ -23,6 +23,16 @@ def _reduce_name(name): ''' converts long name into email address''' return name.split('/')[-1] +def is_invitable_user(email): + '''return email isInvitableUser''' + svc = gapi_cloudidentity.build('cloudidentity_beta') + customer = _get_customerid() + encoded_email = quote_plus(email) + name = f'{customer}/userinvitations/{encoded_email}' + return gapi.call(svc.customers().userinvitations(), 'isInvitableUser', + name=name)['isInvitableUser'] + + def _generic_action(action): '''generic function to call actionable APIs''' svc = gapi_cloudidentity.build('cloudidentity_beta') @@ -105,7 +115,7 @@ def get(): _generic_get('get') -def is_invitable_user(): +def check(): '''gam check userinvitation ''' _generic_get('isInvitableUser') diff --git a/src/gam/gapi/directory/groups.py b/src/gam/gapi/directory/groups.py index e4eb4ccf..735c11e8 100644 --- a/src/gam/gapi/directory/groups.py +++ b/src/gam/gapi/directory/groups.py @@ -7,8 +7,7 @@ from gam import display from gam import gapi from gam.gapi import directory as gapi_directory from gam.gapi import errors as gapi_errors -from gam.gapi.directory import customer as gapi_directory_customer -from gam import utils +from gam.gapi.cloudidentity import userinvitations as gapi_cloudidentity_userinvitations def GroupIsAbuseOrPostmaster(emailAddr): @@ -23,6 +22,7 @@ def create(): cd = gapi_directory.build() body = {'email': gam.normalizeEmailAddressOrUID(sys.argv[3], noUid=True)} gs_get_before_update = got_name = False + verifyNotInvitable = False i = 4 gs_body = {} gs = None @@ -51,6 +51,9 @@ def create(): elif myarg == 'getbeforeupdate': gs_get_before_update = True i += 1 + elif myarg == 'verifynotinvitable': + verifyNotInvitable = True + i += 1 else: if not gs: gs = gam.buildGAPIObject('groupssettings') @@ -60,6 +63,10 @@ def create(): i += 2 if not got_name: body['name'] = body['email'] + if (verifyNotInvitable and + gapi_cloudidentity_userinvitations.get_is_invitable_user(body['email'])): + sys.stderr.write(f'Group not created, {body["email"]} is an unmanaged account\n') + sys.exit(51) print(f'Creating group {body["email"]}') gapi.call(cd.groups(), 'insert', body=body, fields='email') if gs and not GroupIsAbuseOrPostmaster(body['email']): @@ -1138,6 +1145,7 @@ def update(): else: i = 4 use_cd_api = False + verifyNotInvitable = False gs = None gs_body = {} cd_body = {} @@ -1155,6 +1163,9 @@ def update(): elif myarg == 'getbeforeupdate': gs_get_before_update = True i += 1 + elif myarg == 'verifynotinvitable': + verifyNotInvitable = True + i += 1 else: if not gs: gs = gam.buildGAPIObject('groupssettings') @@ -1166,6 +1177,10 @@ def update(): if use_cd_api or ( group.find('@') == -1 ): # group settings API won't take uid so we make sure cd API is used so that we can grab real email. + if (verifyNotInvitable and 'email' in cd_body and + gapi_cloudidentity_userinvitations.get_is_invitable_user(cd_body['email'])): + sys.stderr.write(f'Group {group} not updated, new email {cd_body["email"]} is an unmanaged account\n') + sys.exit(51) group = gapi.call(cd.groups(), 'update', groupKey=group,