docs(07-03): complete GraphUserSearchService plan
- Add 07-03-SUMMARY.md with implementation details and decisions - Update STATE.md: progress 54%, decisions, session, metrics - Update ROADMAP.md: phase 07 now 2/8 summaries
This commit is contained in:
@@ -3,14 +3,14 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: completed
|
status: completed
|
||||||
stopped_at: Completed 07-01-PLAN.md
|
stopped_at: Completed 07-03-PLAN.md
|
||||||
last_updated: "2026-04-07T10:38:10.474Z"
|
last_updated: "2026-04-07T10:40:07.964Z"
|
||||||
last_activity: 2026-04-07 — Roadmap created (Phases 6-9), 10/10 requirements mapped
|
last_activity: 2026-04-07 — Roadmap created (Phases 6-9), 10/10 requirements mapped
|
||||||
progress:
|
progress:
|
||||||
total_phases: 4
|
total_phases: 4
|
||||||
completed_phases: 1
|
completed_phases: 1
|
||||||
total_plans: 13
|
total_plans: 13
|
||||||
completed_plans: 6
|
completed_plans: 7
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -48,6 +48,7 @@ Phase 6 [ ] → Phase 7 [ ] → Phase 8 [ ] → Phase 9 [ ]
|
|||||||
| Phase 06-global-site-selection P04 | 2 | 3 tasks | 6 files |
|
| Phase 06-global-site-selection P04 | 2 | 3 tasks | 6 files |
|
||||||
| Phase 06-global-site-selection P05 | 2 | 1 tasks | 1 files |
|
| Phase 06-global-site-selection P05 | 2 | 1 tasks | 1 files |
|
||||||
| Phase 07-user-access-audit P01 | 5 | 2 tasks | 3 files |
|
| Phase 07-user-access-audit P01 | 5 | 2 tasks | 3 files |
|
||||||
|
| Phase 07-user-access-audit P03 | 2 | 1 tasks | 1 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
||||||
@@ -72,6 +73,9 @@ Decisions are logged in PROJECT.md Key Decisions table.
|
|||||||
- [Phase 06-global-site-selection]: Used reflection to set _hasLocalSiteOverride in PermissionsViewModel test — avoids needing a real SitePickerDialog
|
- [Phase 06-global-site-selection]: Used reflection to set _hasLocalSiteOverride in PermissionsViewModel test — avoids needing a real SitePickerDialog
|
||||||
- [Phase 07-01]: UserAccessEntry is fully denormalized (one row = one user + one object + one permission) for direct DataGrid binding
|
- [Phase 07-01]: UserAccessEntry is fully denormalized (one row = one user + one object + one permission) for direct DataGrid binding
|
||||||
- [Phase 07-01]: IsHighPrivilege and IsExternalUser pre-computed at scan time; GraphUserResult co-located with IGraphUserSearchService interface
|
- [Phase 07-01]: IsHighPrivilege and IsExternalUser pre-computed at scan time; GraphUserResult co-located with IGraphUserSearchService interface
|
||||||
|
- [Phase 07-03]: Minimum 2-character query guard prevents overly broad Graph API requests
|
||||||
|
- [Phase 07-03]: OData single-quote escaping (replace apostrophe with two apostrophes) prevents injection in startsWith filter
|
||||||
|
- [Phase 07-03]: ConsistencyLevel=eventual and Count=true both required for startsWith on Graph directory objects
|
||||||
|
|
||||||
### Pending Todos
|
### Pending Todos
|
||||||
|
|
||||||
@@ -83,6 +87,6 @@ None.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-07T10:38:10.472Z
|
Last session: 2026-04-07T10:40:07.961Z
|
||||||
Stopped at: Completed 07-01-PLAN.md
|
Stopped at: Completed 07-03-PLAN.md
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|||||||
69
.planning/phases/07-user-access-audit/07-03-SUMMARY.md
Normal file
69
.planning/phases/07-user-access-audit/07-03-SUMMARY.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
phase: 07-user-access-audit
|
||||||
|
plan: 03
|
||||||
|
subsystem: graph-user-search-service
|
||||||
|
tags: [graph-api, user-search, people-picker, services]
|
||||||
|
dependency_graph:
|
||||||
|
requires: [07-01]
|
||||||
|
provides: [GraphUserSearchService]
|
||||||
|
affects: [07-04, 07-05]
|
||||||
|
tech_stack:
|
||||||
|
added: []
|
||||||
|
patterns: [Microsoft Graph SDK, OData filter, startsWith, ConsistencyLevel=eventual]
|
||||||
|
key_files:
|
||||||
|
created:
|
||||||
|
- SharepointToolbox/Services/GraphUserSearchService.cs
|
||||||
|
modified: []
|
||||||
|
decisions:
|
||||||
|
- "Minimum 2-character query guard prevents overly broad Graph API requests"
|
||||||
|
- "Single-quote escaping in OData filter prevents injection (replace ' with '')"
|
||||||
|
- "ConsistencyLevel=eventual + Count=true both required for startsWith on directory objects"
|
||||||
|
metrics:
|
||||||
|
duration_minutes: 2
|
||||||
|
completed_date: "2026-04-07"
|
||||||
|
tasks_completed: 1
|
||||||
|
files_created: 1
|
||||||
|
files_modified: 0
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 7 Plan 03: GraphUserSearchService Implementation Summary
|
||||||
|
|
||||||
|
**One-liner:** GraphUserSearchService implements IGraphUserSearchService using GraphClientFactory, querying Graph /users with startsWith OData filter on displayName, mail, and UPN for people-picker autocomplete.
|
||||||
|
|
||||||
|
## What Was Built
|
||||||
|
|
||||||
|
**GraphUserSearchService.cs** — Concrete implementation of IGraphUserSearchService. Queries the Microsoft Graph `/users` endpoint using OData `startsWith` filter across three fields (displayName, mail, userPrincipalName). Sets the required `ConsistencyLevel: eventual` header and `$count=true` parameter mandatory for advanced directory filters. Returns up to `maxResults` (default 10) `GraphUserResult` records ordered by displayName. Guards against queries shorter than 2 characters to prevent broad, wasteful API calls.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
| # | Task | Status | Commit |
|
||||||
|
|---|------|--------|--------|
|
||||||
|
| 1 | Implement GraphUserSearchService | Done | 026b829 |
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
1. **2-character minimum guard** — Queries of 0 or 1 character return an empty list immediately without calling Graph. This prevents overly broad results and unnecessary API calls while the user is still typing.
|
||||||
|
|
||||||
|
2. **OData single-quote escaping** — Query strings replace `'` with `''` before embedding in the OData filter. This prevents OData injection if user input contains apostrophes (e.g., "O'Brien").
|
||||||
|
|
||||||
|
3. **ConsistencyLevel=eventual + Count=true** — Microsoft Graph requires both headers when using `startsWith` on directory objects. Omitting either causes a 400 Bad Request. Both are set together in the request configuration.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `dotnet build SharepointToolbox/SharepointToolbox.csproj` — 0 errors, 0 warnings
|
||||||
|
- GraphUserSearchService.cs implements IGraphUserSearchService confirmed
|
||||||
|
- Uses GraphClientFactory.CreateClientAsync for auth (not raw HTTP)
|
||||||
|
- Empty/short query guard (length < 2) returns Array.Empty<GraphUserResult>()
|
||||||
|
- Filter covers displayName, mail, and userPrincipalName with startsWith
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
Files confirmed present:
|
||||||
|
- FOUND: SharepointToolbox/Services/GraphUserSearchService.cs
|
||||||
|
|
||||||
|
Commits confirmed:
|
||||||
|
- FOUND: 026b829
|
||||||
Reference in New Issue
Block a user