"""What the new policy will actually change, given how CLOAK combines policies. Endpoint (modules/pii/resolvePolicy.ts, modules/agent/routes.ts): - AI prompt part (action, detection rules, AI sites, upload/paste, test mode): a group with its own active policies uses only those; the org-wide prompt rules stop applying to it - device and browser controls (agentControl, the agent's extensionControl): folded across org-wide AND group policies, any "on" wins, so org-wide controls keep applying to every group - within the prompt set in force, ONE action applies to every rule: the strictest one wins Channels (pickChannelPolicy): one active policy per channel applies, the newest; it does not merge. Email: one active email policy per provider (Gmail, Outlook / Microsoft 365). """ from __future__ import annotations from .model import Draft from .platform import catalog from .platform.context import PlatformContext PROVIDER_LABEL = {'google': 'Gmail', 'm365': 'Outlook / Microsoft 365'} def assess(d: Draft, ctx: PlatformContext) -> None: body = d.policyBody if body is None: return d.issue('CREATED_INACTIVE', 'info', 'The policy is proposed switched off. Review it, then turn it on.') if not ctx.policiesKnown: return if d.policyType == 'endpoint': _endpoint(d, body, ctx) elif d.policyType == 'email': provider = body['channels']['email'].get('provider') or 'google' active = [p for p in ctx.policies if p.type == 'email' and (p.provider or 'google') == provider] if active: label = PROVIDER_LABEL.get(provider, provider) d.issue('ONE_ACTIVE_EMAIL_POLICY', 'warning', f'"{active[0].name}" is the active {label} email policy, and only one per provider can be active. ' f'To use this one, turn that one off first.', policy=active[0].name, provider=provider) else: _channel(d, body, ctx) def _endpoint(d: Draft, body: dict, ctx: PlatformContext) -> None: rank = catalog.action_rank() gid = body['groupId'] endpoint = [p for p in ctx.policies if p.type == 'endpoint'] org = [p for p in endpoint if p.groupId == 'default'] own = [p for p in endpoint if p.groupId == gid and gid != 'default'] if gid != 'default': name = ctx.group(gid).name if ctx.group(gid) else gid if not own and org: pl = 'y' if len(org) == 1 else 'ies' msg = (f'Once this policy is on, AI prompts from {name} are checked only by policies for {name}, so the ' f'AI prompt rules of the org-wide polic{pl} ({", ".join(p.name for p in org[:3])}) stop applying ' f'to {name}.') if not _has_prompt_part(body): msg += f' This policy checks no prompts itself, so AI prompts from {name} would only be logged.' msg += (f' Device and browser controls from the org-wide polic{pl} (USB, printing, block lists and ' f'the rest) still apply to {name}.') d.issue('GROUP_REPLACES_ORG_WIDE', 'warning', msg, group=name, orgWide=[p.name for p in org]) else: groups = sorted({p.groupId for p in endpoint if p.groupId != 'default'}) if groups and _has_prompt_part(body): names = [ctx.group(g).name if ctx.group(g) else g for g in groups] d.issue('ORG_WIDE_SKIPS_GROUPS', 'warning', f'Groups with their own policies ({", ".join(names)}) keep their own AI prompt rules, so this ' f'policy\'s prompt checks do not reach them. Its device and browser controls apply to them too.', groups=names) uses_action = bool(body['extensionControl']['detectionRuleIds']) if not uses_action: return if gid != 'default' and not own: return # the group's first policy: once on, it is the only one in force for the group scope = own if gid != 'default' else org if not scope: return new = rank.get(body['action'], 0) strictest = max(scope, key=lambda p: rank.get(p.action, 0)) if rank.get(strictest.action, 0) > new: d.issue('STRICTER_ACTION_ACTIVE', 'warning', f'"{strictest.name}" is already active with "{strictest.action}". One action applies to all rules ' f'and the strictest wins, so this policy will also "{strictest.action}", not "{body["action"]}".', policy=strictest.name, action=strictest.action) elif rank.get(strictest.action, 0) < new: d.issue('ACTION_RAISES_OTHERS', 'warning', f'One action applies to every rule in force and the strictest wins: once this is on, matches of the ' f'other active policies ({", ".join(p.name for p in scope[:3])}) will also "{body["action"]}".', action=body['action']) def _has_prompt_part(body: dict) -> bool: """Whether the policy sets anything the group-replaces-org-wide rule applies to.""" return bool(body['extensionControl']['detectionRuleIds']) or body['allowFileUpload'] is False \ or body['allowCodePaste'] is False or body['action'] != 'log' def _channel(d: Draft, body: dict, ctx: PlatformContext) -> None: cfg = body['channels'][d.policyType] mine = set(cfg.get('connectorIds') or []) for p in ctx.policies: if p.type != d.policyType: continue theirs = set(p.connectorIds) if not mine or not theirs or mine & theirs: d.issue('CHANNEL_POLICY_REPLACED', 'warning', f'"{p.name}" is the active {catalog.TYPE_LABEL[d.policyType]} policy. Only one applies at a time ' f'(the newest), so turning this on replaces it rather than adding to it.', policy=p.name) return