feat(directory): Phase 1 backend — Directory layer + Entra provider#406
Open
larsgeorge-db wants to merge 1 commit into
Open
feat(directory): Phase 1 backend — Directory layer + Entra provider#406larsgeorge-db wants to merge 1 commit into
larsgeorge-db wants to merge 1 commit into
Conversation
This was referenced May 21, 2026
Adds the provider-agnostic Directory layer (manager + provider plug-in interface + EntraIdProvider) and four routes under /api/directory. Backend pieces: - DirectoryProvider ABC + EntraIdProvider that calls Microsoft Graph exclusively through ws.serving_endpoints.http_request(connection_name=...). UC owns OAuth2 client-credentials and token caching; the app stores no client secret. Includes OData escaping, eventual-consistency header for startswith filters, and tight $select projections. - DirectoryManager dispatches to the configured provider, holds a 5-minute in-memory TTL cache keyed on (provider_type, connection_name, kind, query|limit), and invalidates on provider/connection change or explicit settings update. - Pydantic models: Principal (user|group|unknown, id=UPN/displayName, display_name, sub_label), DirectoryStatus, DirectoryTestResult, DirectorySearchResponse, DirectorySettingsUpdate. Settings keys live in app_settings (no Alembic migration): DIRECTORY_PROVIDER_TYPE, DIRECTORY_UC_HTTP_CONNECTION_NAME. - Routes (settings read/write permission-gated): GET /api/directory/status GET /api/directory/search?q=&types=&limit= POST /api/directory/test PUT /api/directory/settings GET /api/directory/uc-http-connections - Shared list_http_connections() helper extracted from workflows route; the existing /api/workflows/http-connections endpoint now delegates. - Manager wired into startup_tasks + DI getter, exposed as DirectoryManagerDep. Tests: - 16 EntraIdProvider tests: OData escaping, $select projections, Principal mapping (UPN -> id for users, displayName -> id for groups), eventual-consistency header, response body shapes (bytes, str, stream, None), Graph error body, transport errors, non-JSON. - 14 DirectoryManager tests: configured-status edge cases (unknown provider type stays not-configured), cache hit (case + whitespace normalisation), cache invalidation when settings change OR when invalidate_cache() called, types filter narrows downstream calls, stub-provider registration proves the abstraction is enough to add a new provider with no manager/route changes. - Backend test conftest now sets non-secret defaults for DATABRICKS_HOST / DATABRICKS_WAREHOUSE_ID / APP_AUDIT_LOG_DIR via setdefault so the suite runs without a local .env. Plan: plans/directory-lookup-and-principal-picker.md (PR #375).
This was referenced May 21, 2026
larsgeorge-db
added a commit
that referenced
this pull request
May 21, 2026
…r scope Documents what shipped under PRs #406 / #407 / #412 / #413 / #416 / #417: - Renames the integration's manager / routes / settings keys in the PRD to match the implementation (Directory layer, /api/directory/*, DIRECTORY_* settings, Settings → Directory tab). - Documents the DirectoryProvider interface and the (DirectoryProviderContext, DirectoryProviderConfig) factory signature so future provider plug-ins know what to implement. - Documents the v1 provider set, which expanded during planning from Entra-only to entra + lakebase + file. The Lakebase table schema and CSV format are included so operators have a single reference. - Preserves story content, the disambiguation rule, both picker modes, storage-compatibility guarantees, and graceful-degradation rules from the PRD body unchanged. - Re-confirms the out-of-scope list (Okta/Ping, service principals, OBO, profile photos, manager hierarchy, role/team Select replacement, CSV bulk import) which the abstraction makes cheap to revisit.
larsgeorge-db
added a commit
that referenced
this pull request
May 21, 2026
…r scope Documents what shipped under PRs #406 / #407 / #412 / #413 / #416 / #417: - Renames the integration's manager / routes / settings keys in the PRD to match the implementation (Directory layer, /api/directory/*, DIRECTORY_* settings, Settings → Directory tab). - Documents the DirectoryProvider interface and the (DirectoryProviderContext, DirectoryProviderConfig) factory signature so future provider plug-ins know what to implement. - Documents the v1 provider set, which expanded during planning from Entra-only to entra + lakebase + file. The Lakebase table schema and CSV format are included so operators have a single reference. - Preserves story content, the disambiguation rule, both picker modes, storage-compatibility guarantees, and graceful-degradation rules from the PRD body unchanged. - Re-confirms the out-of-scope list (Okta/Ping, service principals, OBO, profile photos, manager hierarchy, role/team Select replacement, CSV bulk import) which the abstraction makes cheap to revisit.
b2ee317 to
d2f09fa
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First of two PRs implementing Phase 1 of the directory-lookup-and-principal-picker plan (#375). This PR is the backend foundation; a follow-up PR will add the Settings tab +
PrincipalPicker+ Assign Owner migration.Source PRD: docs/prds/prd-entra-id-graph-integration.md (#335)
Plan: plans/directory-lookup-and-principal-picker.md (#375)
Summary
Directorylayer: provider-agnostic abstraction (DirectoryProviderABC) with a single concrete provider for Microsoft Entra ID. All Graph traffic goes throughws.serving_endpoints.http_request(connection_name=...)so UC owns OAuth2 client-credentials acquisition and token caching — the app stores no secret.DirectoryManagerdispatches to the configured provider and caches results in a 5-min in-memory TTL keyed on(provider_type, connection_name, kind, query|limit), invalidating on settings change or explicitinvalidate_cache().app_settingskey/value table (DIRECTORY_PROVIDER_TYPE,DIRECTORY_UC_HTTP_CONNECTION_NAME) — no Alembic migration./api/directory/*:status,search,test,settings,uc-http-connections. Permission-gated against the existingsettingsfeature.list_http_connections()helper extracted fromworkflows_routes.py; the existing/api/workflows/http-connectionsroute now delegates.setdefaults for required Settings fields so the suite is runnable locally without.env.What's intentionally not here
PrincipalPicker, Settings → Directory tab, Assign Owner migration) — next PR.List[str]shape.Architectural notes
Principal.idis the persisted identifier (UPN/email for users, displayName for groups — not GUIDs).sub_labelcarries the secondary identifier (email/UPN or GUID) for disambiguation.DirectoryStatus.configuredto decide picker mode.(type, id)to survive partial cache hits and the cross-type merge honours the caller's overalllimit.Test plan
cd src && hatch -e dev run pytest backend/src/tests/unit/→ 764 passed, 0 failedtest_entra_id_provider.py(16): OData escaping,$selectprojections, Principal mapping (UPN → id for users, displayName → id for groups), eventual-consistency header, response body shapes (bytes / str / stream / None), Graph error body surfaced asDirectoryError, transport-error surfaces, non-JSON body, empty connection-name guard.test_directory_manager.py(14): unknown-provider-type stays not-configured, dispatches to registered provider, cache hits on case + whitespace normalisation, cache invalidates when settings change orinvalidate_cache()is called,types=["user"]narrows downstream calls, stub-provider registration proves the abstraction is enough to add a provider with no manager/route changes.ruff check --isolated)./api/directory/statusagainst a workspace (left for reviewer or follow-up PR alongside the UI).