From d745aa65f533f81341a0ba2b8056c4a7b170ab7d Mon Sep 17 00:00:00 2001 From: Ross Scroggs Date: Wed, 10 Jun 2026 15:48:00 -0700 Subject: [PATCH] Create, update and delete Cloud Identity policies --- src/GamCommands.txt | 8 +++++ src/GamUpdate.txt | 7 ++++ src/gam/__init__.py | 71 ++++++++++++++++++++++------------------ src/gam/gamlib/glapi.py | 10 ------ src/gam/gamlib/glmsgs.py | 1 + 5 files changed, 55 insertions(+), 42 deletions(-) diff --git a/src/GamCommands.txt b/src/GamCommands.txt index 834428b9..aa7dda49 100644 --- a/src/GamCommands.txt +++ b/src/GamCommands.txt @@ -4378,6 +4378,14 @@ gam show policies [group ] [ou|org|orgunit ] [formatjson] +gam create policy + json + [(ou|orgunit )|(group )|(query )] +gam update policy + json + [(ou|orgunit )|(group )|(query )] +gam delete policies + # Inbound SSO ::= diff --git a/src/GamUpdate.txt b/src/GamUpdate.txt index 57e6d9e6..7e61f516 100644 --- a/src/GamUpdate.txt +++ b/src/GamUpdate.txt @@ -1,3 +1,10 @@ +7.46.00 + +Added commands to create, update and delete Cloud Identity policies for data loss prevention (DLP) rules and detectors. + +* See: https://github.com/GAM-team/GAM/wiki/Cloud-Identity-Policies +* See: https://workspaceupdates.googleblog.com/2026/06/introducing-workspace-policy-api-mutate-endpoints-for-DLP.html + 7.45.00 Added options `isdisabled []`, `disabledafter ` and `disabledbefore ` diff --git a/src/gam/__init__.py b/src/gam/__init__.py index cd558ff2..7ad0a52c 100755 --- a/src/gam/__init__.py +++ b/src/gam/__init__.py @@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki """ __author__ = 'GAM Team ' -__version__ = '7.45.00' +__version__ = '7.46.00' __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' # pylint: disable=wrong-import-position @@ -38526,37 +38526,25 @@ def _checkPoliciesWithDASA(): systemErrorExit(USAGE_ERROR_RC, Msg.COMMAND_NOT_COMPATIBLE_WITH_ENABLE_DASA.format(Act.ToPerform().lower(), Cmd.ARG_CIPOLICIES)) -def _getCIPolicyOrgUnitTarget(cd, myarg, groupEmail): - if groupEmail: - Cmd.Backup() - usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'group')) - targetName, targetResource = _getOrgunitsOrgUnitIdPath(cd, getString(Cmd.OB_ORGUNIT_PATH)) - return (targetName, targetResource) - -def _getCIPolicyGroupTarget(cd, myarg, orgUnit): - if orgUnit: - Cmd.Backup() - usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'ou|org|orgunit')) - targetName = getEmailAddress(returnUIDprefix='uid:') - targetResource = f"groups/{convertEmailAddressToUID(targetName, cd, emailType='group')}" - return (targetName, targetResource) - # gam create policy # json -# [(ou|orgunit )|(group )] +# [(ou|orgunit )|(group )|(query )] # gam update policy # json -# [(ou|orgunit )|(group )] +# [(ou|orgunit )|(group )|(query )] def doCreateUpdateCIPolicy(): _checkPoliciesWithDASA() - ci = buildGAPIObject(API.CLOUDIDENTITY_POLICY_BETA) + ci = buildGAPIObject(API.CLOUDIDENTITY_POLICY) cd = buildGAPIObject(API.DIRECTORY) updateCmd = Act.Get() == Act.UPDATE - groupEmail = orgUnit = None + groupEmail = orgUnit = query = None checkArgumentPresent('json', True) policy = getJSON(['customer', 'type']) if updateCmd: pname = policy.pop('name', None) + if not pname: + Cmd.Backup() + usageErrorExit(Msg.POLICY_NAME_NOT_FOUND) else: policy.pop('name', None) pname = 'New Policy' @@ -38564,6 +38552,8 @@ def doCreateUpdateCIPolicy(): policy['policyQuery'].pop('orgUnitPath', None) policy['policyQuery'].pop('groupEmail', None) policy['policyQuery'].pop('sortOrder', None) + if 'orgUnit' in policy['policyQuery'] or 'group' in policy['policyQuery']: + policy['policyQuery'].pop('query', None) if 'setting' in policy: if 'value' in policy['setting']: policy['setting']['value'].pop('createTime', None) @@ -38576,19 +38566,37 @@ def doCreateUpdateCIPolicy(): while Cmd.ArgumentsRemaining(): myarg = getArgument() if myarg in {'ou', 'org', 'orgunit'}: - orgUnit, targetResource = _getCIPolicyOrgUnitTarget(cd, myarg, groupEmail) - policy.setdefault('policyQuery', {}) - policy['policyQuery'].pop('group', None) - policy['policyQuery']['orgUnit'] = f"orgUnits/{targetResource}" - policy['policyQuery']['query'] = f"entity.org_units.exists(org_unit, org_unit.org_unit_id == orgUnitId('{targetResource}'))" + if groupEmail: + Cmd.Backup() + usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'group')) + if query: + Cmd.Backup() + usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'query')) + orgUnit, targetResource = _getOrgunitsOrgUnitIdPath(cd, getString(Cmd.OB_ORGUNIT_PATH)) + policy['policyQuery'] = {'orgUnit': f"orgUnits/{targetResource}"} elif myarg == 'group': - groupEmail, targetResource = _getCIPolicyGroupTarget(cd, myarg, orgUnit) - policy.setdefault('policyQuery', {}) - policy['policyQuery'].pop('orgUnit', None) - policy['policyQuery']['group'] = f"groups/{targetResource}" - policy['policyQuery']['query'] = f"entity.groups.exists(group, group.group_id == groupId('{targetResource}'))" + if orgUnit: + Cmd.Backup() + usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'ou|org|orgunit')) + if query: + Cmd.Backup() + usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'query')) + groupEmail = getEmailAddress(returnUIDprefix='uid:') + targetResource = f"groups/{convertEmailAddressToUID(groupEmail, cd, emailType='group')}" + policy['policyQuery'] = {'group': f"groups/{targetResource}"} + elif myarg == 'query': + if groupEmail: + Cmd.Backup() + usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'group')) + if orgUnit: + Cmd.Backup() + usageErrorExit(Msg.ARE_MUTUALLY_EXCLUSIVE.format(myarg, 'ou|org|orgunit')) + query = getString(Cmd.OB_QUERY) + policy['policyQuery'] = {'query': query} else: unknownArgumentExit() + if 'policyQuery' not in policy: + missingArgumentExit('ou|org|orgunit|group|query') policy['customer'] = _getCustomersCustomerIdWithC() try: if updateCmd: @@ -38616,11 +38624,10 @@ def doCreateUpdateCIPolicy(): GAPI.notFound, GAPI.permissionDenied, GAPI.internalError) as e: entityActionFailedWarning([Ent.POLICY, pname], str(e)) - # gam delete policies def doDeleteCIPolicies(): _checkPoliciesWithDASA() - ci = buildGAPIObject(API.CLOUDIDENTITY_POLICY_BETA) + ci = buildGAPIObject(API.CLOUDIDENTITY_POLICY) entityList = getEntityList(Cmd.OB_CIPOLICY_NAME_ENTITY) checkForExtraneousArguments() i = 0 diff --git a/src/gam/gamlib/glapi.py b/src/gam/gamlib/glapi.py index 68143261..a60fe71f 100644 --- a/src/gam/gamlib/glapi.py +++ b/src/gam/gamlib/glapi.py @@ -51,7 +51,6 @@ CLOUDIDENTITY_INBOUND_SSO = 'cloudidentityinboundsso' CLOUDIDENTITY_ORGUNITS = 'cloudidentityorgunits' CLOUDIDENTITY_ORGUNITS_BETA = 'cloudidentityorgunitsbeta' CLOUDIDENTITY_POLICY = 'cloudidentitypolicy' -CLOUDIDENTITY_POLICY_BETA = 'cloudidentitypolicybeta' CLOUDIDENTITY_USERINVITATIONS = 'cloudidentityuserinvitations' CLOUDRESOURCEMANAGER = 'cloudresourcemanager' CLOUDRESOURCEMANAGERV1 = 'cloudresourcemanagerv1' @@ -261,7 +260,6 @@ _INFO = { CLOUDIDENTITY_ORGUNITS: {'name': 'Cloud Identity API - OrgUnits', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'}, CLOUDIDENTITY_ORGUNITS_BETA: {'name': 'Cloud Identity API - OrgUnits Beta', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'}, CLOUDIDENTITY_POLICY: {'name': 'Cloud Identity API - Policy', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'}, - CLOUDIDENTITY_POLICY_BETA: {'name': 'Cloud Identity API - Policy Beta', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'}, CLOUDIDENTITY_USERINVITATIONS: {'name': 'Cloud Identity API - User Invitations', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'}, CLOUDRESOURCEMANAGER: {'name': 'Resource Manager API v3', 'version': 'v3', 'v2discovery': True}, CLOUDRESOURCEMANAGERV1: {'name': 'Resource Manager API v1', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudresourcemanager'}, @@ -405,10 +403,6 @@ _CLIENT_SCOPES = [ 'subscopes': READONLY, 'roByDefault': True, 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'}, - {'name': 'Cloud Identity API - Policy Beta', - 'api': CLOUDIDENTITY_POLICY_BETA, - 'offByDefault': True, - 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'}, {'name': 'Cloud Identity API - User Invitations', 'api': CLOUDIDENTITY_USERINVITATIONS, 'subscopes': READONLY, @@ -641,10 +635,6 @@ _SVCACCT_SCOPES = [ # 'subscopes': READONLY, # 'roByDefault': True, # 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'}, -# {'name': 'Cloud Identity API - Policy Beta', -# 'api': CLOUDIDENTITY_POLICY_BETA, -# 'offByDefault': True, -# 'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'}, # {'name': 'Cloud Identity User Invitations API', # 'api': CLOUDIDENTITY_USERINVITATIONS, # 'subscopes': READONLY, diff --git a/src/gam/gamlib/glmsgs.py b/src/gam/gamlib/glmsgs.py index 3b49dc88..40b1e07e 100644 --- a/src/gam/gamlib/glmsgs.py +++ b/src/gam/gamlib/glmsgs.py @@ -462,6 +462,7 @@ PLEASE_CORRECT_YOUR_SYSTEM_TIME = 'Please correct your system time.' PLEASE_ENTER_A_OR_M = 'Please enter a or m ...\n' PLEASE_SELECT_ENTITY_TO_PROCESS = '{0} {1} found, please select the correct one to {2} and specify with {3}' PLEASE_SPECIFY_BUILDING_EXACT_CASE_NAME_OR_ID = 'Please specify building by exact case name or ID.' +POLICY_NAME_NOT_FOUND = 'JSON key "name" not found in JSON data' PREVIEW_ONLY = 'Preview Only' PRIMARY_EMAIL_DID_NOT_MATCH_PATTERN = 'primaryEmail address did not match pattern: {0}' PROCESS = 'process'