167 lines
7.3 KiB
Markdown
167 lines
7.3 KiB
Markdown
|
|
# Claim Mapping Rules Editor
|
||
|
|
|
||
|
|
## Problem
|
||
|
|
|
||
|
|
The server has a claim mapping rules engine (`ClaimMappingService`) that maps arbitrary JWT claims to Cameleer roles and groups during OIDC login. The backend API exists (`/api/v1/admin/claim-mappings` CRUD), but there is no UI for managing these rules. Admins must use curl or Swagger to create, edit, and delete rules.
|
||
|
|
|
||
|
|
## Solution
|
||
|
|
|
||
|
|
Add a rules editor modal to the existing OIDC config page (`/admin/oidc`), triggered from the Claim Mapping section. The modal contains a compact rules table with inline CRUD and a collapsible server-side test panel.
|
||
|
|
|
||
|
|
## Design
|
||
|
|
|
||
|
|
### Trigger
|
||
|
|
|
||
|
|
An "Edit Rules" button added to the bottom of the existing Claim Mapping section on the OIDC config page. Displays a badge with the active rule count (e.g. "3 active rules"). Visible in both read and edit mode of the OIDC form — the rules modal manages its own state independently of the OIDC form's edit/save lifecycle.
|
||
|
|
|
||
|
|
### Modal — Rules Table
|
||
|
|
|
||
|
|
A full-width modal dialog with the title "Claim Mapping Rules" and a subtitle explaining their purpose.
|
||
|
|
|
||
|
|
**Table columns:**
|
||
|
|
|
||
|
|
| Column | Content | Width |
|
||
|
|
|--------|---------|-------|
|
||
|
|
| `#` | Priority number (display only, derived from order) | 30px |
|
||
|
|
| Claim | Claim name in monospace code style | auto |
|
||
|
|
| Match | Match type pill: `equals`, `contains`, `regex` | auto |
|
||
|
|
| Value | Match value in monospace | auto |
|
||
|
|
| Action | Action pill: `assign role` (green/success), `add to group` (amber) | auto |
|
||
|
|
| Target | Role name or group name | auto |
|
||
|
|
| Actions | Reorder arrows + edit pencil + delete x | ~80px |
|
||
|
|
|
||
|
|
**Action icons per row:**
|
||
|
|
|
||
|
|
- Up/down arrow buttons for reordering. Up hidden on first row, down hidden on last row. Moving a rule swaps priorities locally (saved to server on Apply).
|
||
|
|
- Pencil icon switches the row to inline edit mode (same field layout as the add row).
|
||
|
|
- X icon deletes the rule locally after a `ConfirmDialog` confirmation (saved to server on Apply).
|
||
|
|
|
||
|
|
**Inline add form:**
|
||
|
|
|
||
|
|
A row at the bottom of the table with input fields matching the table columns:
|
||
|
|
|
||
|
|
- `Input` — claim name (text, placeholder "Claim name")
|
||
|
|
- `Select` — match type (equals / contains / regex)
|
||
|
|
- `Input` — match value (text, placeholder "Match value")
|
||
|
|
- `Select` — action (assign role / add to group)
|
||
|
|
- `Select` — target, populated from existing roles (when action is "assign role") or existing groups (when action is "add to group"). Includes a placeholder option ("Select role..." / "Select group..."). Switching action resets the target selection.
|
||
|
|
- `Button` — "+ Add" (disabled until claim, value, and target are filled)
|
||
|
|
|
||
|
|
New rules are assigned a priority one higher than the current maximum.
|
||
|
|
|
||
|
|
**Inline edit mode:**
|
||
|
|
|
||
|
|
When the pencil icon is clicked, the row's static cells become editable inputs (same controls as the add form, including the target Select dropdown). The action icons change to a checkmark (save) and x (cancel). Saving updates local state only — persisted to server on Apply.
|
||
|
|
|
||
|
|
**Empty state:**
|
||
|
|
|
||
|
|
When no rules exist, show a centered message: "No claim mapping rules configured. Rules let you assign roles and groups based on JWT claims." with the add form below.
|
||
|
|
|
||
|
|
### Modal — Test Panel
|
||
|
|
|
||
|
|
A collapsible section at the bottom of the modal, separated by a heavier border. Collapsed by default. Toggle via a clickable header row showing "Test Rules — Paste a decoded JWT to preview which rules would fire".
|
||
|
|
|
||
|
|
**Layout when expanded:**
|
||
|
|
|
||
|
|
Side-by-side split:
|
||
|
|
|
||
|
|
- **Left:** `textarea` for pasting decoded JWT claims as JSON. Placeholder shows a sample JSON object.
|
||
|
|
- **Right:** Results panel with:
|
||
|
|
- Each matched rule listed: rule number, claim name, match description, arrow, action and target
|
||
|
|
- "Effective" summary line: combined roles and groups
|
||
|
|
- If no rules matched: "No rules matched — would fall back to default roles: [roles]"
|
||
|
|
|
||
|
|
**"Test" button** below the textarea triggers `POST /api/v1/admin/claim-mappings/test`. The evaluation runs server-side using `ClaimMappingService.evaluate()` — the same code path as the production OIDC login flow in `OidcAuthController.applyClaimMappings()`.
|
||
|
|
|
||
|
|
**Visual feedback:** While test results are displayed, matched rows in the table above get a subtle green background tint and a checkmark in place of their action icons.
|
||
|
|
|
||
|
|
### Local Edit + Apply Pattern
|
||
|
|
|
||
|
|
All changes (add, edit, delete, reorder) modify local state only. No API calls are made until the admin clicks Apply.
|
||
|
|
|
||
|
|
- **On modal open:** server rules are cloned into local state.
|
||
|
|
- **Cancel:** discards all local changes and closes the modal.
|
||
|
|
- **Apply:** diffs local state against server state — creates new rules (temp IDs), updates changed rules, deletes removed rules. On success, shows a toast and closes the modal. On failure, shows an error toast and keeps the modal open.
|
||
|
|
|
||
|
|
### Reordering
|
||
|
|
|
||
|
|
Up/down arrow buttons on each row. Clicking an arrow swaps the priority values of two adjacent rules in local state.
|
||
|
|
|
||
|
|
Priority is a server-side integer. The UI displays it as the row number (`#` column) — admins never edit the number directly.
|
||
|
|
|
||
|
|
### New Backend Endpoint
|
||
|
|
|
||
|
|
```
|
||
|
|
POST /api/v1/admin/claim-mappings/test
|
||
|
|
Content-Type: application/json
|
||
|
|
Authorization: Bearer <admin-token>
|
||
|
|
|
||
|
|
{
|
||
|
|
"sub": "user-42",
|
||
|
|
"email": "jane@acme.com",
|
||
|
|
"department": "engineering",
|
||
|
|
"groups": ["frontend", "design"]
|
||
|
|
}
|
||
|
|
|
||
|
|
Response 200:
|
||
|
|
{
|
||
|
|
"matchedRules": [
|
||
|
|
{
|
||
|
|
"ruleId": "uuid",
|
||
|
|
"priority": 1,
|
||
|
|
"claim": "email",
|
||
|
|
"matchType": "regex",
|
||
|
|
"matchValue": ".*@acme\\.com$",
|
||
|
|
"action": "assignRole",
|
||
|
|
"target": "OPERATOR"
|
||
|
|
}
|
||
|
|
],
|
||
|
|
"effectiveRoles": ["OPERATOR"],
|
||
|
|
"effectiveGroups": [],
|
||
|
|
"fallback": false
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
The endpoint:
|
||
|
|
|
||
|
|
- Requires ADMIN role (same as other claim mapping endpoints)
|
||
|
|
- Loads all rules from the database
|
||
|
|
- Calls `ClaimMappingService.evaluate(rules, claims)` — production code path
|
||
|
|
- Maps `MappingResult` list to the response DTO
|
||
|
|
- Returns `fallback: true` when no rules matched (UI shows default roles message)
|
||
|
|
- Read-only — no side effects, no user creation, no role assignment
|
||
|
|
|
||
|
|
### Error Handling
|
||
|
|
|
||
|
|
- **Invalid JSON in test textarea:** Show inline validation error, disable Test button
|
||
|
|
- **API errors on CRUD:** Toast notifications (consistent with rest of admin UI)
|
||
|
|
- **ConfirmDialog on delete:** Type-to-confirm not needed for rules (they're low-risk, easily recreated). Simple "Delete this rule?" confirm/cancel.
|
||
|
|
|
||
|
|
## Files to Create/Modify
|
||
|
|
|
||
|
|
### Backend
|
||
|
|
|
||
|
|
- `ClaimMappingAdminController.java` — add `POST /test` endpoint
|
||
|
|
- New DTO for test response (inline record or separate class)
|
||
|
|
|
||
|
|
### Frontend
|
||
|
|
|
||
|
|
- New component: `ClaimMappingRulesModal.tsx` — the modal with table, inline CRUD, and test panel
|
||
|
|
- New CSS module: `ClaimMappingRulesModal.module.css`
|
||
|
|
- New API hooks: `useClaimMappingRules()`, `useCreateRule()`, `useUpdateRule()`, `useDeleteRule()`, `useTestRules()` — React Query hooks for the claim mapping API
|
||
|
|
- Modify: `OidcConfigPage.tsx` — add "Edit Rules" button in the Claim Mapping section, render the modal
|
||
|
|
|
||
|
|
### Not in Scope
|
||
|
|
|
||
|
|
- Drag-and-drop reordering (up/down arrows are sufficient for 1-10 rules)
|
||
|
|
- Groups Claim field (rules can already match any claim including group claims)
|
||
|
|
- Bulk import/export of rules
|
||
|
|
- Rule templates or presets
|
||
|
|
|
||
|
|
## Visual Reference
|
||
|
|
|
||
|
|
Mockups available in `.superpowers/brainstorm/172364-1776174996/content/`:
|
||
|
|
|
||
|
|
- `claim-rules-modal.html` — initial layout (trigger button + table)
|
||
|
|
- `claim-rules-modal-v2.html` — full modal with test panel expanded and match highlighting
|