# scim-bridge — Helix (SCIM 2.0) → NexusCRM public REST

PingCAP tenant integration glue. Terminates Helix's generic SCIM push and
drives NexusCRM through its existing production data API. NexusCRM core is
untouched; retires when the upstream L1 SCIM AppService lands
(NexusCRM `docs/design/scim-l1-rebuild.md`).

## Operator setup (the whole "configure a key" flow)
1. In NexusCRM (admin): create a **confidential Connected App** with
   `client_credentials` grant, a least-privilege **RunAs user**, and scopes
   covering `/services/data`. The RunAs user needs exactly:
   - `ApiEnabled`
   - **Read + Create + Edit on User**, with field-level access to the mapped
     fields (Username, Email, FirstName, LastName, IsActive, Title, Phone,
     MobilePhone, Department, Division, Company, ManagerId, Alias, ProfileId,
     TimeZoneSidKey, LocaleSidKey, EmailEncodingKey, LanguageLocaleKey) — the
     bridge queries Users before every create/update/deactivate
   - **Read on Profile, PermissionSet, `_System_ProfilePermission`,
     `_System_PermissionSetPermission`, ObjectPermissions, FieldPermissions,
     PermissionSetCustomPermission, ProfileCustomPermission, and
     PermissionSetRecordTypeVisibility** (to re-prove zero access before every
     first-time create)
   - no delete permission (the bridge never deletes) and no password
     permissions (it never writes Password)
2. Run the bridge: `python3 scim_bridge.py` with env
   `BRIDGE_NEXUS_BASE_URL=<nexuscrm origin>` (see `BridgeConfig` for the rest:
   listen host/port, the fixed `SCIM Zero Access` onboarding profile, locale
   defaults, deactivation breaker + exemption list).
3. In Helix: add a Generic SCIM 2.0 target with base URL
   `https://<bridge>/scim/v2` and API token `<client_id>.<client_secret>`.

**TLS is mandatory outside loopback.** The bearer carries the connected-app
secret, so (a) the bridge refuses a plaintext non-loopback
`BRIDGE_NEXUS_BASE_URL` (override only behind a trusted same-host TLS proxy
via `BRIDGE_ALLOW_INSECURE_HTTP=1`), and (b) the bridge itself must be
published to Helix behind a TLS-terminating proxy (nginx/ALB) — never expose
its plain-HTTP listener on a network.

## Behavior
- Create: SSO-only accounts (no Password ever sent); required NexusCRM fields
  (Alias/ProfileId/locales) auto-defaulted; the Profile is fixed to
  `SCIM Zero Access` and any other configured profile fails startup. Before
  every first-time create, the bridge re-proves the Profile is unique,
  active/non-system/Standard, owns exactly one active PermissionSet, and has no
  grants across the seven Seed-owned permission planes; drift fails closed.
  Re-POST remains idempotent.
- Update: PUT/PATCH map name/title/phones/department/division/org/manager
  (manager resolved by email → ManagerId).
- Deprovision: SCIM DELETE and `active=false` both PATCH `IsActive=false`
  (engine forbids User delete; deactivation revokes sessions transactionally).
- Safety: never-deactivate exemption list (bootstrap admin always included) +
  deactivation circuit breaker (default 25/hour).
- Auth: stateless/secretless — inbound bearer is the connected-app credential,
  exchanged (and cached) for a NexusCRM token; platform enforces all authz.

## Tests
`python3 -m unittest tests.test_scim_bridge` — full SCIM flows over real HTTP
against an in-memory fake NexusCRM.
