TenantAtlas/specs/051-entra-group-directory-cache/contracts/admin-directory-groups.openapi.yaml
ahmido bc846d7c5c 051-entra-group-directory-cache (#57)
Summary

Adds a tenant-scoped Entra Groups “Directory Cache” to enable DB-only group name resolution across the app (no render-time Graph calls), plus sync runs + observability.

What’s included
	•	Entra Groups cache
	•	New entra_groups storage (tenant-scoped) for group metadata (no memberships).
	•	Retention semantics: groups become stale / retained per spec (no hard delete on first miss).
	•	Group Sync Runs
	•	New “Group Sync Runs” UI (list + detail) with tenant isolation (403 on cross-tenant access).
	•	Manual “Sync Groups” action: creates/reuses a run, dispatches job, DB notification with “View run” link.
	•	Scheduled dispatcher command wired in console.php.
	•	DB-only label resolution (US3)
	•	Shared EntraGroupLabelResolver with safe fallback Unresolved (…last8) and UUID guarding.
	•	Refactors to prefer cached names (no typeahead / no live Graph) in:
	•	Tenant RBAC group selects
	•	Policy version assignments widget
	•	Restore results + restore wizard group mapping labels

Safety / Guardrails
	•	No render-time Graph calls: fail-hard guard test verifies UI paths don’t call GraphClientInterface during page render.
	•	Tenant isolation & authorization: policies + scoped queries enforced (cross-tenant access returns 403, not 404).
	•	Data minimization: only group metadata is cached (no membership/owners).

Tests / Verification
	•	Added/updated tests under tests/Feature/DirectoryGroups and tests/Unit/DirectoryGroups:
	•	Start sync → run record + job dispatch + upserts
	•	Retention purge semantics
	•	Scheduled dispatch wiring
	•	Render-time Graph guard
	•	UI/resource access isolation
	•	Ran:
	•	./vendor/bin/pint --dirty
	•	./vendor/bin/sail artisan test tests/Feature/DirectoryGroups
	•	./vendor/bin/sail artisan test tests/Unit/DirectoryGroups

Notes / Follow-ups
	•	UI polish remains (picker/lookup UX, consistent progress widget/toasts across modules, navigation grouping).
	•	pr-gate checklist still has non-blocking open items (mostly UX/ops polish); requirements gate is green.

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@adsmac.local>
Reviewed-on: #57
2026-01-11 23:24:12 +00:00

136 lines
4.0 KiB
YAML

openapi: 3.0.3
info:
title: TenantPilot Admin - Directory Groups (v1)
version: 0.1.0
description: |
Admin surfaces for tenant-scoped Entra groups cache (metadata only).
Rendering uses cached data only; sync runs read the directory via app-only auth.
paths:
/admin/tenants/{tenantId}/directory/groups:
get:
summary: List cached groups
parameters:
- $ref: '#/components/parameters/TenantId'
- in: query
name: q
schema: { type: string }
description: Search by display name or ID
- in: query
name: stale
schema: { type: boolean }
description: Filter to stale groups only
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/EntraGroup'
/admin/tenants/{tenantId}/directory/groups/{entraGroupId}:
get:
summary: Get group details
parameters:
- $ref: '#/components/parameters/TenantId'
- in: path
name: entraGroupId
required: true
schema: { type: string }
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/EntraGroup'
/admin/tenants/{tenantId}/directory/groups/sync:
post:
summary: Start a manual groups sync
parameters:
- $ref: '#/components/parameters/TenantId'
responses:
'202':
description: Accepted (run created)
content:
application/json:
schema:
$ref: '#/components/schemas/EntraGroupSyncRun'
/admin/tenants/{tenantId}/directory/groups/runs:
get:
summary: List group sync runs
parameters:
- $ref: '#/components/parameters/TenantId'
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/EntraGroupSyncRun'
/admin/tenants/{tenantId}/directory/groups/runs/{runId}:
get:
summary: Get group sync run details
parameters:
- $ref: '#/components/parameters/TenantId'
- in: path
name: runId
required: true
schema: { type: integer }
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/EntraGroupSyncRun'
components:
parameters:
TenantId:
in: path
name: tenantId
required: true
schema: { type: integer }
schemas:
EntraGroup:
type: object
required: [tenant_id, entra_group_id, display_name, last_seen_at]
properties:
tenant_id: { type: integer }
entra_group_id: { type: string }
display_name: { type: string }
group_type: { type: string }
security_enabled: { type: boolean, nullable: true }
mail_enabled: { type: boolean, nullable: true }
group_types:
type: array
items: { type: string }
last_seen_at: { type: string, format: date-time }
EntraGroupSyncRun:
type: object
required: [tenant_id, selection_key, status, created_at]
properties:
id: { type: integer }
tenant_id: { type: integer }
initiated_by_user_id: { type: integer, nullable: true }
selection_key: { type: string }
status: { type: string }
started_at: { type: string, format: date-time, nullable: true }
finished_at: { type: string, format: date-time, nullable: true }
observed_count: { type: integer, nullable: true }
upserted_count: { type: integer, nullable: true }
error_count: { type: integer, nullable: true }
error_category: { type: string, nullable: true }
error_summary: { type: string, nullable: true }
created_at: { type: string, format: date-time }