docs(02-02): complete PermissionsService plan — models, interface, scan engine
- Created 02-02-SUMMARY.md with full execution record - Updated STATE.md with decisions (CSOM type constraints) and progress - Updated ROADMAP.md (phase 02: 4/7 summaries, In Progress) - Marked PERM-07 complete in REQUIREMENTS.md
This commit is contained in:
@@ -26,11 +26,11 @@ Requirements for initial release. Each maps to roadmap phases.
|
|||||||
|
|
||||||
- [x] **PERM-01**: User can scan permissions on a single SharePoint site with configurable depth
|
- [x] **PERM-01**: User can scan permissions on a single SharePoint site with configurable depth
|
||||||
- [x] **PERM-02**: User can scan permissions across multiple selected sites in one operation
|
- [x] **PERM-02**: User can scan permissions across multiple selected sites in one operation
|
||||||
- [ ] **PERM-03**: Permissions scan includes owners, members, guests, external users, and broken inheritance
|
- [x] **PERM-03**: Permissions scan includes owners, members, guests, external users, and broken inheritance
|
||||||
- [x] **PERM-04**: User can choose to include or exclude inherited permissions
|
- [x] **PERM-04**: User can choose to include or exclude inherited permissions
|
||||||
- [x] **PERM-05**: User can export permissions report to CSV (raw data)
|
- [x] **PERM-05**: User can export permissions report to CSV (raw data)
|
||||||
- [x] **PERM-06**: User can export permissions report to interactive HTML (sortable, filterable, groupable by user)
|
- [x] **PERM-06**: User can export permissions report to interactive HTML (sortable, filterable, groupable by user)
|
||||||
- [ ] **PERM-07**: SharePoint 5,000-item list view threshold handled via pagination — no silent failures on large libraries
|
- [x] **PERM-07**: SharePoint 5,000-item list view threshold handled via pagination — no silent failures on large libraries
|
||||||
|
|
||||||
### Storage
|
### Storage
|
||||||
|
|
||||||
@@ -129,11 +129,11 @@ Which phases cover which requirements. Updated during roadmap creation.
|
|||||||
| FOUND-12 | Phase 1 | Complete |
|
| FOUND-12 | Phase 1 | Complete |
|
||||||
| PERM-01 | Phase 2 | Complete |
|
| PERM-01 | Phase 2 | Complete |
|
||||||
| PERM-02 | Phase 2 | Complete |
|
| PERM-02 | Phase 2 | Complete |
|
||||||
| PERM-03 | Phase 2 | Pending |
|
| PERM-03 | Phase 2 | Complete |
|
||||||
| PERM-04 | Phase 2 | Complete |
|
| PERM-04 | Phase 2 | Complete |
|
||||||
| PERM-05 | Phase 2 | Complete |
|
| PERM-05 | Phase 2 | Complete |
|
||||||
| PERM-06 | Phase 2 | Complete |
|
| PERM-06 | Phase 2 | Complete |
|
||||||
| PERM-07 | Phase 2 | Pending |
|
| PERM-07 | Phase 2 | Complete |
|
||||||
| STOR-01 | Phase 3 | Pending |
|
| STOR-01 | Phase 3 | Pending |
|
||||||
| STOR-02 | Phase 3 | Pending |
|
| STOR-02 | Phase 3 | Pending |
|
||||||
| STOR-03 | Phase 3 | Pending |
|
| STOR-03 | Phase 3 | Pending |
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: planning
|
status: planning
|
||||||
stopped_at: Completed 02-01-PLAN.md — Wave 0 test scaffold for permissions phase
|
stopped_at: Completed 02-02-PLAN.md
|
||||||
last_updated: "2026-04-02T11:55:45.013Z"
|
last_updated: "2026-04-02T11:56:31.288Z"
|
||||||
last_activity: 2026-04-02 — Roadmap created, requirements mapped, all 42 v1 requirements assigned to phases
|
last_activity: 2026-04-02 — Roadmap created, requirements mapped, all 42 v1 requirements assigned to phases
|
||||||
progress:
|
progress:
|
||||||
total_phases: 5
|
total_phases: 5
|
||||||
completed_phases: 1
|
completed_phases: 1
|
||||||
total_plans: 15
|
total_plans: 15
|
||||||
completed_plans: 11
|
completed_plans: 12
|
||||||
percent: 13
|
percent: 13
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -62,6 +62,7 @@ Progress: [█░░░░░░░░░] 13%
|
|||||||
| Phase 02-permissions P05 | 1min | 1 tasks | 3 files |
|
| Phase 02-permissions P05 | 1min | 1 tasks | 3 files |
|
||||||
| Phase 02-permissions P03 | 1min | 1 tasks | 5 files |
|
| Phase 02-permissions P03 | 1min | 1 tasks | 5 files |
|
||||||
| Phase 02-permissions P01 | 5min | 2 tasks | 9 files |
|
| Phase 02-permissions P01 | 5min | 2 tasks | 9 files |
|
||||||
|
| Phase 02-permissions P02 | 7min | 2 tasks | 4 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
||||||
@@ -101,6 +102,8 @@ Recent decisions affecting current work:
|
|||||||
- [Phase 02-permissions]: InternalsVisibleTo added to AssemblyInfo.cs — required for test project to access internal DeriveAdminUrl; plan omitted this assembly attribute
|
- [Phase 02-permissions]: InternalsVisibleTo added to AssemblyInfo.cs — required for test project to access internal DeriveAdminUrl; plan omitted this assembly attribute
|
||||||
- [Phase 02-permissions]: Export service stubs created in Plan 02-01 so test project compiles before Plan 03 implementation
|
- [Phase 02-permissions]: Export service stubs created in Plan 02-01 so test project compiles before Plan 03 implementation
|
||||||
- [Phase 02-permissions]: Principal.Email removed from CSOM load expression — Email only exists on User subtype, not Principal base class
|
- [Phase 02-permissions]: Principal.Email removed from CSOM load expression — Email only exists on User subtype, not Principal base class
|
||||||
|
- [Phase 02-permissions]: Folder is not a SecurableObject in CSOM — ListItem used for permission extraction — Required by CSOM type system; Folder inherits from ClientObject not SecurableObject
|
||||||
|
- [Phase 02-permissions]: Principal.Email excluded from CSOM Include — email not needed for PermissionEntry — Principal base type has no Email property; only User subtype does; avoids CS1061
|
||||||
|
|
||||||
### Pending Todos
|
### Pending Todos
|
||||||
|
|
||||||
@@ -114,6 +117,6 @@ None yet.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-02T11:55:36.761Z
|
Last session: 2026-04-02T11:56:31.286Z
|
||||||
Stopped at: Completed 02-01-PLAN.md — Wave 0 test scaffold for permissions phase
|
Stopped at: Completed 02-02-PLAN.md
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|||||||
152
.planning/phases/02-permissions/02-02-SUMMARY.md
Normal file
152
.planning/phases/02-permissions/02-02-SUMMARY.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
---
|
||||||
|
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<PermissionEntry>)
|
||||||
|
- 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<string> 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)
|
||||||
Reference in New Issue
Block a user