Files
Sharepoint-Toolbox/.planning/milestones/v1.0-phases/02-permissions/02-VERIFICATION.md
Dev 655bb79a99
All checks were successful
Release zip package / release (push) Successful in 10s
chore: complete v1.0 milestone
Archive 5 phases (36 plans) to milestones/v1.0-phases/.
Archive roadmap, requirements, and audit to milestones/.
Evolve PROJECT.md with shipped state and validated requirements.
Collapse ROADMAP.md to one-line milestone summary.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 09:15:14 +02:00

168 lines
13 KiB
Markdown

---
phase: 02-permissions
verified: 2026-04-02T14:30:00Z
status: human_needed
score: 6/7 must-haves verified automatically
human_verification:
- test: "Run the application and confirm all 7 UI checklist items in Plan 07"
expected: "Permissions tab visible with scan options, DataGrid, export buttons disabled when empty, French locale translates all labels, Cancel button disabled at idle, View Sites opens SitePickerDialog"
why_human: "UI layout, localization rendering, live dialog behavior, and button enabled-state cannot be verified programmatically"
- test: "Confirm Export CSV / Export HTML buttons are localized (or intentionally hardcoded)"
expected: "Buttons either use the rad.csv.perms / rad.html.perms localization keys, or the decision to use hardcoded 'Export CSV' / 'Export HTML' was intentional"
why_human: "XAML uses hardcoded English strings 'Export CSV' and 'Export HTML' instead of localization bindings — minor i18n gap that needs human decision on whether it is acceptable"
---
# Phase 2: Permissions Verification Report
**Phase Goal:** Implement the Permissions tab — a full SharePoint permissions scanner with multi-site support, CSV/HTML export, and scan options. Port of the PowerShell Generate-PnPSitePermissionRpt function.
**Verified:** 2026-04-02T14:30:00Z
**Status:** human_needed
**Re-verification:** No — initial verification
---
## Goal Achievement
### Observable Truths
| # | Truth | Status | Evidence |
|---|-------|--------|----------|
| 1 | User can scan permissions on a single SharePoint site with configurable depth (PERM-01) | VERIFIED | `PermissionsService.ScanSiteAsync` fully implemented; SiteUrl bound in XAML; `ScanOptions(FolderDepth)` passed through |
| 2 | User can scan permissions across multiple selected sites in one operation (PERM-02) | VERIFIED | `PermissionsViewModel.RunOperationAsync` loops over `SelectedSites`; `PermissionsViewModelTests.StartScanAsync_WithMultipleSiteUrls_CallsServiceOncePerUrl` passes; `SiteListService` + `SitePickerDialog` wired end-to-end |
| 3 | Permissions scan includes owners, members, guests, external users, and broken inheritance (PERM-03) | VERIFIED | `PermissionsService` scans site collection admins, web, lists, folders; `#EXT#` detection in `PermissionEntryHelper.IsExternalUser`; `PrincipalType` set correctly; 7 classification tests pass |
| 4 | User can choose to include or exclude inherited permissions (PERM-04) | VERIFIED | `IncludeInherited` bool bound in XAML via `{Binding IncludeInherited}`; passed to `ScanOptions`; `ExtractPermissionsAsync` skips non-unique objects when `IncludeInherited=false` |
| 5 | User can export permissions report to CSV (PERM-05) | VERIFIED | `CsvExportService.BuildCsv` + `WriteAsync` implemented; UTF-8 BOM; merges rows by (Users, PermissionLevels, GrantedThrough); all 3 `CsvExportServiceTests` pass |
| 6 | User can export permissions report to interactive HTML (PERM-06) | VERIFIED | `HtmlExportService.BuildHtml` produces self-contained HTML with inline CSS/JS, stats cards, type badges, external-user pills; all 3 `HtmlExportServiceTests` pass |
| 7 | SharePoint 5,000-item list view threshold handled via pagination — no silent failures (PERM-07) | VERIFIED | `PermissionsService.GetFolderPermissionsAsync` uses `SharePointPaginationHelper.GetAllItemsAsync` with `RowLimit 500` pagination — never raw list enumeration (grep confirmed line 222) |
**Score:** 7/7 truths verified (all automated; 2 items need human confirmation for UI/i18n quality)
---
### Required Artifacts
| Artifact | Provided By | Status | Details |
|----------|------------|--------|---------|
| `SharepointToolbox/Core/Models/PermissionEntry.cs` | Plan 02 | VERIFIED | 9-field record; compiles; referenced by tests |
| `SharepointToolbox/Core/Models/ScanOptions.cs` | Plan 02 | VERIFIED | Immutable record with correct defaults |
| `SharepointToolbox/Core/Models/SiteInfo.cs` | Plan 03 | VERIFIED | `record SiteInfo(string Url, string Title)` |
| `SharepointToolbox/Services/IPermissionsService.cs` | Plan 02 | VERIFIED | Interface with `ScanSiteAsync` signature |
| `SharepointToolbox/Services/PermissionsService.cs` | Plan 02 | VERIFIED | 341 lines; implements all 5 scan paths |
| `SharepointToolbox/Services/ISiteListService.cs` | Plan 03 | VERIFIED | Interface with `GetSitesAsync` signature |
| `SharepointToolbox/Services/SiteListService.cs` | Plan 03 | VERIFIED | `DeriveAdminUrl` implemented; error wrapping present |
| `SharepointToolbox/Services/Export/CsvExportService.cs` | Plan 04 | VERIFIED | Merge logic + RFC 4180 escaping + UTF-8 BOM |
| `SharepointToolbox/Services/Export/HtmlExportService.cs` | Plan 04 | VERIFIED | Self-contained HTML; no external deps; external-user class |
| `SharepointToolbox/Core/Helpers/PermissionEntryHelper.cs` | Plan 01 | VERIFIED | `IsExternalUser`, `FilterPermissionLevels`, `IsSharingLinksGroup` |
| `SharepointToolbox/ViewModels/Tabs/PermissionsViewModel.cs` | Plan 06 | VERIFIED | `FeatureViewModelBase` subclass; 309 lines; all commands present |
| `SharepointToolbox/Views/Dialogs/SitePickerDialog.xaml.cs` | Plan 06 | VERIFIED | Loads sites via `ISiteListService`; filter; CheckBox; OK/Cancel |
| `SharepointToolbox/Views/Tabs/PermissionsView.xaml` | Plan 07 | VERIFIED | Left config panel + right DataGrid + StatusBar; localized |
| `SharepointToolbox/Views/Tabs/PermissionsView.xaml.cs` | Plan 07 | VERIFIED | DI wiring; `GetRequiredService<PermissionsViewModel>()`; dialog factory |
| `SharepointToolbox/App.xaml.cs` | Plan 07 | VERIFIED | All Phase 2 DI registrations present; `Func<TenantProfile, SitePickerDialog>` factory registered |
| `SharepointToolbox/MainWindow.xaml` | Plan 07 | VERIFIED | `PermissionsTabItem` uses `x:Name`; no `FeatureTabBase` stub |
| `SharepointToolbox/MainWindow.xaml.cs` | Plan 07 | VERIFIED | `PermissionsTabItem.Content = serviceProvider.GetRequiredService<PermissionsView>()` |
| `SharepointToolbox/Localization/Strings.resx` | Plan 05 | VERIFIED | 15 Phase 2 keys present (grp.scan.opts, btn.gen.perms, perm.sites.selected, etc.) |
| `SharepointToolbox/Localization/Strings.fr.resx` | Plan 05 | VERIFIED | 15 keys with French translations (e.g., "Options d'analyse", "Analyser les dossiers") |
| `SharepointToolbox/Localization/Strings.Designer.cs` | Plan 05 | PARTIAL | 15 new static properties present; `tab_permissions` property absent (key exists in resx, MainWindow binds via `TranslationSource` directly — low impact) |
| Test scaffold (5 files) | Plan 01 | VERIFIED | All exist; classification tests pass; ViewModel test passes |
---
### Key Link Verification
| From | To | Via | Status | Evidence |
|------|----|-----|--------|----------|
| `PermissionsService.cs` | `SharePointPaginationHelper.GetAllItemsAsync` | Folder enumeration | WIRED | Line 222: `await foreach (var item in SharePointPaginationHelper.GetAllItemsAsync(...))` |
| `PermissionsService.cs` | `ExecuteQueryRetryHelper.ExecuteQueryRetryAsync` | All CSOM round-trips | WIRED | 7 call sites in service (lines 52, 86, 125, 217, 245, 283) |
| `PermissionsService.cs` | `PermissionEntryHelper.IsExternalUser` | User classification | WIRED | Line 314 |
| `PermissionsService.cs` | `PermissionEntryHelper.FilterPermissionLevels` | Level filtering | WIRED | Line 304 |
| `PermissionsService.cs` | `PermissionEntryHelper.IsSharingLinksGroup` | Group skipping | WIRED | Line 299 |
| `SiteListService.cs` | `SessionManager.GetOrCreateContextAsync` | Admin context acquisition | WIRED | Line 41 |
| `SiteListService.cs` | `Microsoft.Online.SharePoint.TenantAdministration.Tenant` | `GetSitePropertiesFromSharePoint` | WIRED | Line 49: `new Tenant(adminCtx)` |
| `PermissionsViewModel.cs` | `IPermissionsService.ScanSiteAsync` | RunOperationAsync loop | WIRED | Line 189 |
| `PermissionsViewModel.cs` | `CsvExportService.WriteAsync` | ExportCsvCommand handler | WIRED | Line 252 |
| `PermissionsViewModel.cs` | `HtmlExportService.WriteAsync` | ExportHtmlCommand handler | WIRED | Line 275 |
| `SitePickerDialog.xaml.cs` | `ISiteListService.GetSitesAsync` | Window.Loaded handler | WIRED | Line 42 |
| `PermissionsView.xaml.cs` | `PermissionsViewModel` | `GetRequiredService<PermissionsViewModel>()` | WIRED | Line 14 |
| `PermissionsView.xaml.cs` | `SitePickerDialog` | `OpenSitePickerDialog` factory | WIRED | Lines 16-19 |
| `App.xaml.cs` | Phase 2 services | `AddTransient<IPermissionsService, PermissionsService>()` etc. | WIRED | Lines 92-100 |
---
### Requirements Coverage
| Requirement | Source Plans | Description | Status | Evidence |
|-------------|-------------|-------------|--------|----------|
| PERM-01 | 02-01, 02-02, 02-05, 02-06, 02-07 | Single-site scan with configurable depth | SATISFIED | `PermissionsService.ScanSiteAsync`; SiteUrl XAML binding; ScanOptions wired |
| PERM-02 | 02-01, 02-03, 02-06, 02-07 | Multi-site scan | SATISFIED | `SiteListService`; `SitePickerDialog`; loop in `RunOperationAsync`; test passes |
| PERM-03 | 02-01, 02-02, 02-07 | Owners, members, guests, external users, broken inheritance | SATISFIED | Site collection admins path; `#EXT#` detection; `PrincipalType` assignment; 7 classification tests pass |
| PERM-04 | 02-01, 02-02, 02-05, 02-06, 02-07 | Include/exclude inherited permissions | SATISFIED | `IncludeInherited` checkbox bound; `ScanOptions` record passed; `ExtractPermissionsAsync` gate |
| PERM-05 | 02-01, 02-04, 02-07 | CSV export | SATISFIED | `CsvExportService` with merge, RFC 4180 escaping, UTF-8 BOM; 3 tests pass; `ExportCsvCommand` wired |
| PERM-06 | 02-01, 02-04, 02-07 | HTML export | SATISFIED | `HtmlExportService` self-contained HTML; inline CSS/JS; stats cards; external-user pills; 3 tests pass; `ExportHtmlCommand` wired |
| PERM-07 | 02-02, 02-07 | 5,000-item list view threshold — pagination | SATISFIED | `SharePointPaginationHelper.GetAllItemsAsync` called in `GetFolderPermissionsAsync`; `RowLimit 500` CAML |
All 7 PERM requirements: SATISFIED
---
### Anti-Patterns Found
| File | Line | Pattern | Severity | Impact |
|------|------|---------|----------|--------|
| `PermissionsView.xaml` | 80, 84 | Hardcoded `Content="Export CSV"` and `Content="Export HTML"` instead of localization bindings | Info | French locale users see English button labels; `rad.csv.perms` and `rad.html.perms` keys exist in resx and Designer.cs but are unused in XAML |
| `Strings.Designer.cs` | n/a | Missing `tab_permissions` static property (key exists in resx) | Info | No functional impact — `TranslationSource.Instance["tab.permissions"]` resolves correctly at runtime via ResourceManager; Designer.cs property is just a typed convenience accessor |
No Blocker or Warning severity anti-patterns found.
---
### Human Verification Required
#### 1. Full UI visual checkpoint
**Test:** Run the application (`dotnet run --project SharepointToolbox` or F5). Navigate to the Permissions tab.
**Expected:**
- Tab is labelled "Permissions" (or "Permissions" in French) and shows scan options panel + empty DataGrid, not "Coming soon"
- Scan Options panel shows: Site URL input, "View Sites" button, "Scan Folders" checkbox, "Include Inherited Permissions" checkbox, "Recursive (subsites)" checkbox, "Folder depth" input, "Maximum (all levels)" checkbox
- "Generate Report" and "Cancel" buttons present
- "Export CSV" and "Export HTML" buttons are disabled (grayed out) with no results
- Click "View Sites" — SitePickerDialog opens (auth error expected if not connected — must not crash)
- Switch to French (Settings tab) — all labels in Permissions tab change to French text
**Why human:** Visual appearance, disabled-state behavior, and locale rendering cannot be verified programmatically.
#### 2. Export button localization decision
**Test:** In the running application (French locale), check the text on the Export buttons.
**Expected:** Either the buttons read "CSV" / "HTML" (acceptable if intentional) or the team decides to bind them to `rad.csv.perms` / `rad.html.perms`.
**Why human:** The XAML has `Content="Export CSV"` and `Content="Export HTML"` hardcoded — the localization keys exist but are not used. This is a minor i18n gap requiring a team decision, not a blocker.
---
### Test Results
| Test class | Tests | Passed | Skipped | Failed |
|-----------|-------|--------|---------|--------|
| `PermissionEntryClassificationTests` | 7 | 7 | 0 | 0 |
| `CsvExportServiceTests` | 3 | 3 | 0 | 0 |
| `HtmlExportServiceTests` | 3 | 3 | 0 | 0 |
| `PermissionsViewModelTests` | 1 | 1 | 0 | 0 |
| `SiteListServiceTests` | 2 | 2 | 0 | 0 |
| `PermissionsServiceTests` | 2 | 0 | 2 | 0 |
| **Full suite** | **63** | **60** | **3** | **0** |
Skipped tests are intentional live-CSOM stubs (require a real SharePoint context).
---
### Gaps Summary
No gaps blocking goal achievement. All 7 PERM requirements are implemented with real, substantive code. All key links are wired. All critical service chains are verified.
Two minor informational items were found:
1. Export buttons in `PermissionsView.xaml` use hardcoded English strings instead of the localization keys that exist in the resx files. This causes the buttons to stay in English when switching to French. The keys `rad.csv.perms` ("CSV") and `rad.html.perms` ("HTML") do exist and resolve correctly — they just aren't bound. This is a cosmetic i18n gap, not a functional failure.
2. `Strings.Designer.cs` is missing the `tab_permissions` typed property (the key exists in both resx files and the MainWindow binding resolves it correctly at runtime via `TranslationSource`).
---
*Verified: 2026-04-02T14:30:00Z*
*Verifier: Claude (gsd-verifier)*