--- phase: 02-permissions plan: 02 subsystem: permissions tags: [csom, sharepoint, permissions, scan-engine, pnp, c-sharp] # Dependency graph requires: - phase: 02-01 provides: PermissionEntryHelper (IsExternalUser, FilterPermissionLevels, IsSharingLinksGroup) provides: - PermissionEntry record — flat data model for one permission assignment - ScanOptions record — immutable scan configuration with IncludeInherited/ScanFolders/FolderDepth/IncludeSubsites - IPermissionsService interface — contract enabling ViewModel mocking in tests - PermissionsService implementation — full CSOM scan engine, port of PS Generate-PnPSitePermissionRpt affects: - 02-04 (PermissionsViewModel uses IPermissionsService) - 02-05 (Export services work on IReadOnlyList) - 02-06 (SitePickerDialog feeds site URLs into PermissionsService) - 02-07 (Full integration wires PermissionsService into DI) # Tech tracking tech-stack: added: [] patterns: - "CSOM batched Include() load pattern — one round-trip per SecurableObject via ctx.Load + ExecuteQueryRetryHelper" - "Async folder enumeration via SharePointPaginationHelper.GetAllItemsAsync (never raw CSOM list enumeration)" - "HashSet ExcludedLists for O(1) system list filtering" - "PrincipalType detection via PermissionEntryHelper.IsExternalUser before CSOM PrincipalType enum" key-files: created: - SharepointToolbox/Core/Models/PermissionEntry.cs - SharepointToolbox/Core/Models/ScanOptions.cs - SharepointToolbox/Services/IPermissionsService.cs - SharepointToolbox/Services/PermissionsService.cs modified: [] key-decisions: - "Folder enumeration uses ListItem (SecurableObject) not Folder — Folder is not a SecurableObject in CSOM; ListItem.Folder provides metadata while ListItem itself holds role assignments" - "Principal.Email excluded from CSOM Include — Principal base type has no Email property; only User subtype does; email not needed for PermissionEntry fields" - "FolderDepth=999 is the sentinel for unlimited depth — avoids nullable int and matches PS reference behavior" - "Subsite enumeration clones ClientContext via ctx.Clone(subweb.Url) — each subsite needs its own context for CSOM scoped operations" patterns-established: - "CSOM batched load: always batch ctx.Load with all required sub-properties in one call before ExecuteQueryRetryAsync" - "ExcludedLists HashSet: new service that filters SharePoint objects uses StringComparer.OrdinalIgnoreCase HashSet for O(1) exclusion" - "ct.ThrowIfCancellationRequested() at the start of every private async method" requirements-completed: - PERM-01 - PERM-03 - PERM-04 - PERM-07 # Metrics duration: 7min completed: 2026-04-02 --- # Phase 2 Plan 2: PermissionsService Scan Engine Summary **CSOM scan engine implementing all 5 SharePoint permission scan paths (site collection admins, web, lists, folders, subsites) as a faithful C# port of the PowerShell Generate-PnPSitePermissionRpt function** ## Performance - **Duration:** 7 min - **Started:** 2026-04-02T11:48:39Z - **Completed:** 2026-04-02T11:54:58Z - **Tasks:** 2 - **Files modified:** 4 ## Accomplishments - Defined PermissionEntry (9-field record), ScanOptions (4-field config record), and IPermissionsService interface — foundational contracts for all subsequent Phase 2 plans - Implemented PermissionsService with full scan logic: site collection admins, web, lists, folders (via SharePointPaginationHelper), and subsites - All CSOM round-trips use ExecuteQueryRetryHelper.ExecuteQueryRetryAsync; folder enumeration uses SharePointPaginationHelper.GetAllItemsAsync (never raw iteration) - Limited Access filtering, sharing links group exclusion, external user detection, and 34-item ExcludedLists set all implemented ## Task Commits Each task was committed atomically: 1. **Task 1: Define data models and IPermissionsService interface** - `4a6594d` (feat) 2. **Task 2: Implement PermissionsService scan engine** - `9f2e2f9` (fix — linter auto-fixed CSOM type errors pre-commit) ## Files Created/Modified - `SharepointToolbox/Core/Models/PermissionEntry.cs` — Flat record for one permission assignment (9 string/bool positional fields) - `SharepointToolbox/Core/Models/ScanOptions.cs` — Immutable scan config: IncludeInherited, ScanFolders, FolderDepth, IncludeSubsites - `SharepointToolbox/Services/IPermissionsService.cs` — Interface with ScanSiteAsync enabling ViewModel mocking - `SharepointToolbox/Services/PermissionsService.cs` — Full CSOM engine: 340 lines, 5 private helpers, 34-item ExcludedLists ## Decisions Made - `Folder` is not a `SecurableObject` in CSOM — folder permissions are extracted via `ListItem` (which IS a SecurableObject); `item.Folder` provides name/URL metadata only - `Principal.Email` excluded from batched Include — `Principal` base type lacks Email; only `User` subtype has it; email was not needed for PermissionEntry fields - `FolderDepth=999` used as sentinel for unlimited depth scanning - Subsite enumeration clones ClientContext via `ctx.Clone(subweb.Url)` for proper CSOM scoping ## Deviations from Plan ### Auto-fixed Issues **1. [Rule 1 - Bug] Principal.Email not available on RoleAssignment.Member** - **Found during:** Task 2 (PermissionsService implementation) - **Issue:** The plan's CSOM Include expression included `ra => ra.Member.Email` — Principal base type has no Email property (only User subtype does) - **Fix:** Removed Email from the batched Include; email is not needed for any PermissionEntry field - **Files modified:** SharepointToolbox/Services/PermissionsService.cs - **Verification:** dotnet build passes with 0 errors - **Committed in:** 9f2e2f9 **2. [Rule 1 - Bug] Folder is not a SecurableObject in CSOM** - **Found during:** Task 2 (GetFolderPermissionsAsync) - **Issue:** `ExtractPermissionsAsync(ctx, folder, ...)` failed — Folder does not inherit from SecurableObject in Microsoft.SharePoint.Client - **Fix:** Changed to pass `item` (ListItem, which IS a SecurableObject) to ExtractPermissionsAsync; kept `item.Folder` load for ServerRelativeUrl/Name metadata only - **Files modified:** SharepointToolbox/Services/PermissionsService.cs - **Verification:** dotnet build passes with 0 errors - **Committed in:** 9f2e2f9 --- **Total deviations:** 2 auto-fixed (2 Rule 1 bugs — CSOM API type constraints) **Impact on plan:** Both fixes were necessary for correct CSOM usage. Folder permission extraction is semantically equivalent — ListItem holds the same role assignments as Folder. No scope creep. ## Issues Encountered - Pre-existing test failures (6): CsvExportService and HtmlExportService tests throw NotImplementedException — these are intentional stubs from Plan 01 to be implemented in Plan 03. No regression introduced by this plan. ## User Setup Required None - no external service configuration required. ## Next Phase Readiness - PermissionEntry, ScanOptions, IPermissionsService, and PermissionsService are available for Plans 02-04 (ViewModel), 02-05 (Export), 02-06 (SitePicker), and 02-07 (full integration) - All Phase 1 tests remain at 53 passing (plus 4 skipping, 6 pre-existing Plan 03 stubs failing) - IPermissionsService is mockable — PermissionsViewModelTests can be unblocked in Plan 04 --- *Phase: 02-permissions* *Completed: 2026-04-02* ## Self-Check: PASSED - FOUND: SharepointToolbox/Core/Models/PermissionEntry.cs - FOUND: SharepointToolbox/Core/Models/ScanOptions.cs - FOUND: SharepointToolbox/Services/IPermissionsService.cs - FOUND: SharepointToolbox/Services/PermissionsService.cs - FOUND: .planning/phases/02-permissions/02-02-SUMMARY.md - FOUND: commit 4a6594d (feat(02-02): define models and interface) - FOUND: commit 9f2e2f9 (fix(02-01): PermissionsService + export stubs)