Implements LIST `$expand` parity with GET by forwarding caller-provided, contract-allowlisted expands. Key changes: - Entra Admin Roles scan now requests `expand=principal` for role assignments so `principal.displayName` can render. - `$expand` normalization/sanitization: top-level comma split (commas inside balanced parentheses preserved), trim, dedupe, allowlist exact match, caps (max 10 tokens, max 200 chars/token). - Diagnostics when expands are removed/truncated (non-prod warning, production low-noise). Tests: - Adds/extends unit coverage for Graph contract sanitization, list request shaping, and the EntraAdminRolesReportService. Spec artifacts included under `specs/112-list-expand-parity/`. Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #136
128 lines
5.6 KiB
Markdown
128 lines
5.6 KiB
Markdown
# Implementation Plan: Graph Contracts — LIST `$expand` Parity Fix
|
|
|
|
**Branch**: `112-list-expand-parity` | **Date**: 2026-02-25 | **Spec**: `specs/112-list-expand-parity/spec.md`
|
|
**Input**: Feature specification from `specs/112-list-expand-parity/spec.md`
|
|
|
|
## Summary
|
|
|
|
Bring LIST query capability parity with GET by forwarding caller-provided, contract-allowlisted `$expand` on `GraphClientInterface::listPolicies()`. Improve `$expand` normalization (top-level comma split + trim + dedupe) and enforce safety caps. Fix the Entra Admin Roles report by explicitly requesting `expand=principal` for `entraRoleAssignments`, allowing principal display names to render correctly.
|
|
|
|
## Technical Context
|
|
|
|
**Language/Version**: PHP 8.4.x
|
|
**Primary Dependencies**: Laravel 12, custom Microsoft Graph integration, Filament v5 (Livewire v4 compliant)
|
|
**Storage**: PostgreSQL (Sail) — no schema changes for this feature
|
|
**Testing**: Pest v4 (`vendor/bin/sail artisan test --compact`)
|
|
**Target Platform**: Web application (Laravel + Filament)
|
|
**Project Type**: web
|
|
**Performance Goals**: Prevent pathological query option inputs (bounded `$expand`)
|
|
**Constraints**: Backwards compatible; no behavior change unless `expand` is explicitly provided
|
|
**Scale/Scope**: Tenant-scoped Graph reads; low fan-out but called from reports
|
|
|
|
## Constitution Check
|
|
|
|
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
|
|
|
|
PASS — no violations expected.
|
|
|
|
- **Read/write separation**: This feature is read-only (query shape change only).
|
|
- **Single Contract Path to Graph**: Graph calls remain through `GraphClientInterface`; allowlists remain in `config/graph_contracts.php`.
|
|
- **Tenant isolation**: No cross-tenant reads; only changes outbound query parameters for already-tenant-scoped calls.
|
|
- **Deterministic capabilities**: Allowlist is exact-match and test-covered; no implicit expansions.
|
|
- **Ops/Observability**: No new `OperationRun` usage; diagnostics are logs only and low-noise in production.
|
|
|
|
Re-check post-design: PASS.
|
|
|
|
## Project Structure
|
|
|
|
### Documentation (this feature)
|
|
|
|
```text
|
|
specs/112-list-expand-parity/
|
|
├── plan.md
|
|
├── research.md
|
|
├── data-model.md
|
|
├── quickstart.md
|
|
├── contracts/
|
|
│ └── graph-client-listPolicies-options.schema.json
|
|
└── tasks.md
|
|
```
|
|
|
|
### Source Code (repository root)
|
|
|
|
```text
|
|
app/
|
|
├── Services/
|
|
│ ├── EntraAdminRoles/
|
|
│ │ └── EntraAdminRolesReportService.php
|
|
│ └── Graph/
|
|
│ ├── GraphClientInterface.php
|
|
│ ├── GraphContractRegistry.php
|
|
│ ├── GraphLogger.php
|
|
│ ├── GraphResponse.php
|
|
│ └── MicrosoftGraphClient.php
|
|
|
|
config/
|
|
└── graph_contracts.php
|
|
|
|
tests/
|
|
├── Unit/
|
|
│ ├── GraphContractRegistryTest.php
|
|
│ ├── MicrosoftGraphClientListPoliciesSelectTest.php
|
|
│ └── EntraAdminRolesReportServiceTest.php
|
|
```
|
|
|
|
**Structure Decision**: Single Laravel web application. No new packages/modules.
|
|
|
|
## Phase 0 — Outline & Research (complete)
|
|
|
|
See `specs/112-list-expand-parity/research.md`.
|
|
|
|
Key resolved clarifications / decisions:
|
|
- `$expand` normalization supports string + array inputs.
|
|
- String inputs split on **top-level commas only** (commas inside balanced parentheses are not separators).
|
|
- Safety caps: max 10 allowlisted expand tokens; max 200 chars per token.
|
|
- Diagnostics: warning in non-prod, debug in prod, structured context.
|
|
|
|
## Phase 1 — Design & Contracts (complete)
|
|
|
|
Artifacts:
|
|
- `specs/112-list-expand-parity/data-model.md`
|
|
- `specs/112-list-expand-parity/contracts/graph-client-listPolicies-options.schema.json`
|
|
- `specs/112-list-expand-parity/quickstart.md`
|
|
|
|
Agent context update:
|
|
- Run `.specify/scripts/bash/update-agent-context.sh copilot` after Phase 1 artifacts are current.
|
|
|
|
## Phase 2 — Implementation Plan (execute next)
|
|
|
|
1) Update `GraphContractRegistry::sanitizeQuery()`
|
|
- Normalize `$expand` input:
|
|
- Accept `string|string[]`.
|
|
- If string: split on **top-level commas only** (ignore commas inside balanced parentheses); trim whitespace.
|
|
- De-dupe tokens; drop empty.
|
|
- Enforce `maxTokenLen=200` (drop tokens exceeding the cap).
|
|
- Filter via `allowed_expand` exact-match allowlist (no partial matching).
|
|
- Enforce `maxItems=10` after allowlist (keep first N allowed).
|
|
- Emit diagnostics (warn in non-prod, debug in prod) when tokens are removed/truncated.
|
|
|
|
2) Update `MicrosoftGraphClient::listPolicies()`
|
|
- Accept `expand` in `$options` and forward it into `$queryInput` as `'$expand'`.
|
|
- Preserve existing behavior when `expand` is not provided.
|
|
|
|
3) Fix Entra Admin Roles report
|
|
- In `EntraAdminRolesReportService::fetchRoleAssignments()`, pass `expand => 'principal'` alongside resolved tenant graph options.
|
|
- Keep the current fallback behavior (`Unknown`) for true upstream missing-data cases.
|
|
|
|
4) Improve discoverability of list query options
|
|
- Expand PHPDoc on `GraphClientInterface::listPolicies()` (and align with `getPolicy()` docs) to describe supported keys and the `expand` input shape.
|
|
|
|
5) Add regression tests (Pest)
|
|
- `GraphContractRegistryTest`: add cases for top-level comma splitting (including commas inside parentheses), dedupe, cap behavior, and non-prod diagnostic log emission.
|
|
- `MicrosoftGraphClientListPoliciesSelectTest`: add assertions that an allowlisted LIST `$expand` is present in the outbound query.
|
|
- `EntraAdminRolesReportServiceTest`: ensure principal display names are used when returned, and ensure the graph client is invoked with `expand=principal`.
|
|
|
|
6) Validate
|
|
- `vendor/bin/sail bin pint --dirty --format agent`
|
|
- Run the focused tests listed in `specs/112-list-expand-parity/quickstart.md`.
|