---
phase: 08-simplified-permissions
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- SharepointToolbox/Core/Models/RiskLevel.cs
- SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs
- SharepointToolbox/Core/Models/PermissionSummary.cs
- SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs
autonomous: true
requirements:
- SIMP-01
- SIMP-02
must_haves:
truths:
- "RiskLevel enum distinguishes High, Medium, Low, and ReadOnly access tiers"
- "PermissionLevelMapping maps all standard SharePoint role names to plain-language labels and risk levels"
- "SimplifiedPermissionEntry wraps PermissionEntry with computed simplified labels and risk level without modifying the original record"
- "PermissionSummary groups permission entries by risk level with counts"
- "Unknown/custom role names fall back to the raw name with a Medium risk level"
artifacts:
- path: "SharepointToolbox/Core/Models/RiskLevel.cs"
provides: "Risk level classification enum"
contains: "enum RiskLevel"
- path: "SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs"
provides: "Static mapping from SP role names to plain-language labels"
contains: "class PermissionLevelMapping"
- path: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs"
provides: "Presentation wrapper for PermissionEntry with simplified fields"
contains: "class SimplifiedPermissionEntry"
- path: "SharepointToolbox/Core/Models/PermissionSummary.cs"
provides: "Aggregation model for summary counts by risk level"
contains: "record PermissionSummary"
key_links:
- from: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs"
to: "SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs"
via: "Static method call to resolve labels and risk level"
pattern: "PermissionLevelMapping\\.Get"
- from: "SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs"
to: "SharepointToolbox/Core/Models/PermissionEntry.cs"
via: "Wraps original entry as Inner property"
pattern: "PermissionEntry Inner"
---
Define the data models and mapping layer for simplified permissions: RiskLevel enum, PermissionLevelMapping helper, SimplifiedPermissionEntry wrapper, and PermissionSummary aggregation model.
Purpose: All subsequent plans import these types. The mapping layer is the core of SIMP-01 (plain-language labels) and SIMP-02 (risk level color coding). PermissionEntry is immutable and NOT modified — SimplifiedPermissionEntry wraps it as a presentation concern.
Output: RiskLevel.cs, PermissionLevelMapping.cs, SimplifiedPermissionEntry.cs, PermissionSummary.cs
@C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/dev/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
From SharepointToolbox/Core/Models/PermissionEntry.cs:
```csharp
namespace SharepointToolbox.Core.Models;
public record PermissionEntry(
string ObjectType, // "Site Collection" | "Site" | "List" | "Folder"
string Title,
string Url,
bool HasUniquePermissions,
string Users, // Semicolon-joined display names
string UserLogins, // Semicolon-joined login names
string PermissionLevels, // Semicolon-joined role names (Limited Access already removed)
string GrantedThrough, // "Direct Permissions" | "SharePoint Group: "
string PrincipalType // "SharePointGroup" | "User" | "External User"
);
```
From SharepointToolbox/Core/Helpers/PermissionEntryHelper.cs:
```csharp
public static class PermissionEntryHelper
{
public static bool IsExternalUser(string loginName);
public static IReadOnlyList FilterPermissionLevels(IEnumerable levels);
public static bool IsSharingLinksGroup(string loginName);
}
```
Task 1: Create RiskLevel enum and PermissionLevelMapping helper
SharepointToolbox/Core/Models/RiskLevel.cs, SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs
Create `SharepointToolbox/Core/Models/RiskLevel.cs`:
```csharp
namespace SharepointToolbox.Core.Models;
///
/// Classifies a SharePoint permission level by its access risk.
/// Used for color coding in both WPF DataGrid and HTML export.
///
public enum RiskLevel
{
/// Full Control, Site Collection Administrator — can delete site, manage permissions.
High,
/// Contribute, Edit, Design — can modify content.
Medium,
/// Read, Restricted View — can view but not modify.
Low,
/// View Only — most restricted legitimate access.
ReadOnly
}
```
Create `SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs`:
```csharp
using SharepointToolbox.Core.Models;
namespace SharepointToolbox.Core.Helpers;
///
/// Maps SharePoint built-in permission level names to human-readable labels and risk levels.
/// Used by SimplifiedPermissionEntry and export services to translate raw role names
/// into plain-language descriptions that non-technical users can understand.
///
public static class PermissionLevelMapping
{
///
/// Result of looking up a SharePoint role name.
///
public record MappingResult(string Label, RiskLevel RiskLevel);
///
/// Known SharePoint built-in permission level mappings.
/// Keys are case-insensitive via the dictionary comparer.
///
private static readonly Dictionary Mappings = new(StringComparer.OrdinalIgnoreCase)
{
// High risk — full administrative access
["Full Control"] = new("Full control (can manage everything)", RiskLevel.High),
["Site Collection Administrator"] = new("Site collection admin (full control)", RiskLevel.High),
// Medium risk — can modify content
["Contribute"] = new("Can edit files and list items", RiskLevel.Medium),
["Edit"] = new("Can edit files, lists, and pages", RiskLevel.Medium),
["Design"] = new("Can edit pages and use design tools", RiskLevel.Medium),
["Approve"] = new("Can approve content and list items", RiskLevel.Medium),
["Manage Hierarchy"] = new("Can create sites and manage pages", RiskLevel.Medium),
// Low risk — read access
["Read"] = new("Can view files and pages", RiskLevel.Low),
["Restricted Read"] = new("Can view pages only (no download)", RiskLevel.Low),
// Read-only — most restricted
["View Only"] = new("Can view files in browser only", RiskLevel.ReadOnly),
["Restricted View"] = new("Restricted view access", RiskLevel.ReadOnly),
};
///
/// Gets the human-readable label and risk level for a SharePoint role name.
/// Returns the mapped result for known roles; for unknown/custom roles,
/// returns the raw name as-is with Medium risk level.
///
public static MappingResult GetMapping(string roleName)
{
if (string.IsNullOrWhiteSpace(roleName))
return new MappingResult(roleName, RiskLevel.Low);
return Mappings.TryGetValue(roleName.Trim(), out var result)
? result
: new MappingResult(roleName.Trim(), RiskLevel.Medium);
}
///
/// Resolves a semicolon-delimited PermissionLevels string into individual mapping results.
/// This handles the PermissionEntry.PermissionLevels format (e.g. "Full Control; Contribute").
///
public static IReadOnlyList GetMappings(string permissionLevels)
{
if (string.IsNullOrWhiteSpace(permissionLevels))
return Array.Empty();
return permissionLevels
.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.Select(GetMapping)
.ToList();
}
///
/// Returns the highest (most dangerous) risk level from a semicolon-delimited permission levels string.
/// Used for row-level color coding when an entry has multiple roles.
///
public static RiskLevel GetHighestRisk(string permissionLevels)
{
var mappings = GetMappings(permissionLevels);
if (mappings.Count == 0) return RiskLevel.Low;
// High < Medium < Low < ReadOnly in enum order — Min gives highest risk
return mappings.Min(m => m.RiskLevel);
}
///
/// Converts a semicolon-delimited PermissionLevels string into a simplified labels string.
/// E.g. "Full Control; Contribute" becomes "Full control (can manage everything); Can edit files and list items"
///
public static string GetSimplifiedLabels(string permissionLevels)
{
var mappings = GetMappings(permissionLevels);
return string.Join("; ", mappings.Select(m => m.Label));
}
}
```
Design notes:
- Case-insensitive lookup handles variations in SharePoint role name casing
- Unknown/custom roles default to Medium (conservative — forces admin review)
- GetHighestRisk uses enum ordering (High=0 is most dangerous) for row-level color
- Semicolon-split methods handle the PermissionEntry.PermissionLevels format directly
cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5
RiskLevel.cs contains 4-value enum (High, Medium, Low, ReadOnly). PermissionLevelMapping.cs has GetMapping, GetMappings, GetHighestRisk, and GetSimplifiedLabels. All standard SP roles mapped. Unknown roles fallback to Medium. Project compiles.
Task 2: Create SimplifiedPermissionEntry wrapper and PermissionSummary model
SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs, SharepointToolbox/Core/Models/PermissionSummary.cs
Create `SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs`:
```csharp
using SharepointToolbox.Core.Helpers;
namespace SharepointToolbox.Core.Models;
///
/// Presentation wrapper around PermissionEntry that adds simplified labels
/// and risk level classification without modifying the immutable source record.
/// Used as the DataGrid ItemsSource when simplified mode is active.
///
public class SimplifiedPermissionEntry
{
/// The original immutable PermissionEntry.
public PermissionEntry Inner { get; }
///
/// Human-readable labels for the permission levels.
/// E.g. "Can edit files and list items" instead of "Contribute".
///
public string SimplifiedLabels { get; }
///
/// The highest risk level across all permission levels on this entry.
/// Used for row-level color coding.
///
public RiskLevel RiskLevel { get; }
///
/// Individual mapping results for each permission level in the entry.
/// Used when detailed breakdown per-role is needed.
///
public IReadOnlyList Mappings { get; }
// ── Passthrough properties for DataGrid binding ──
public string ObjectType => Inner.ObjectType;
public string Title => Inner.Title;
public string Url => Inner.Url;
public bool HasUniquePermissions => Inner.HasUniquePermissions;
public string Users => Inner.Users;
public string UserLogins => Inner.UserLogins;
public string PermissionLevels => Inner.PermissionLevels;
public string GrantedThrough => Inner.GrantedThrough;
public string PrincipalType => Inner.PrincipalType;
public SimplifiedPermissionEntry(PermissionEntry entry)
{
Inner = entry;
Mappings = PermissionLevelMapping.GetMappings(entry.PermissionLevels);
SimplifiedLabels = PermissionLevelMapping.GetSimplifiedLabels(entry.PermissionLevels);
RiskLevel = PermissionLevelMapping.GetHighestRisk(entry.PermissionLevels);
}
///
/// Creates SimplifiedPermissionEntry wrappers for a collection of entries.
///
public static IReadOnlyList WrapAll(
IEnumerable entries)
{
return entries.Select(e => new SimplifiedPermissionEntry(e)).ToList();
}
}
```
Create `SharepointToolbox/Core/Models/PermissionSummary.cs`:
```csharp
namespace SharepointToolbox.Core.Models;
///
/// Summary counts of permission entries grouped by risk level.
/// Displayed in the summary panel when simplified mode is active.
///
public record PermissionSummary(
/// Label for this group (e.g. "High Risk", "Read Only").
string Label,
/// The risk level this group represents.
RiskLevel RiskLevel,
/// Number of permission entries at this risk level.
int Count,
/// Number of distinct users at this risk level.
int DistinctUsers
);
///
/// Computes PermissionSummary groups from SimplifiedPermissionEntry collections.
///
public static class PermissionSummaryBuilder
{
///
/// Risk level display labels.
///
private static readonly Dictionary Labels = new()
{
[RiskLevel.High] = "High Risk",
[RiskLevel.Medium] = "Medium Risk",
[RiskLevel.Low] = "Low Risk",
[RiskLevel.ReadOnly] = "Read Only",
};
///
/// Builds summary counts grouped by risk level from a collection of simplified entries.
/// Always returns all 4 risk levels, even if count is 0, for consistent UI binding.
///
public static IReadOnlyList Build(
IEnumerable entries)
{
var grouped = entries
.GroupBy(e => e.RiskLevel)
.ToDictionary(g => g.Key, g => g.ToList());
return Enum.GetValues()
.Select(level =>
{
var items = grouped.GetValueOrDefault(level, new List());
var distinctUsers = items
.SelectMany(e => e.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries))
.Select(u => u.Trim())
.Where(u => u.Length > 0)
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
return new PermissionSummary(
Label: Labels[level],
RiskLevel: level,
Count: items.Count,
DistinctUsers: distinctUsers);
})
.ToList();
}
}
```
Design notes:
- SimplifiedPermissionEntry is a class (not record) so it can have passthrough properties for DataGrid binding
- All original PermissionEntry fields are exposed as passthrough properties — DataGrid columns bind identically
- SimplifiedLabels and RiskLevel are computed once at construction — no per-render cost
- PermissionSummaryBuilder.Build always returns 4 entries (one per RiskLevel) for consistent summary panel layout
- DistinctUsers uses case-insensitive comparison for login deduplication
cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5
SimplifiedPermissionEntry wraps PermissionEntry with SimplifiedLabels, RiskLevel, Mappings, and all passthrough properties. PermissionSummary + PermissionSummaryBuilder provide grouped counts. Project compiles cleanly. PermissionEntry.cs is NOT modified.
- `dotnet build SharepointToolbox/SharepointToolbox.csproj` succeeds with 0 errors
- RiskLevel.cs has High, Medium, Low, ReadOnly values
- PermissionLevelMapping has 11 known role mappings with labels and risk levels
- SimplifiedPermissionEntry wraps PermissionEntry (Inner property) without modifying it
- PermissionSummaryBuilder.Build returns 4 summary entries (one per risk level)
- No changes to PermissionEntry.cs
All 4 files compile cleanly. The mapping and wrapper layer is complete: downstream plans (08-02 through 08-05) can import RiskLevel, PermissionLevelMapping, SimplifiedPermissionEntry, and PermissionSummary without ambiguity. PermissionEntry remains immutable and unmodified.