Plans cover plain-language permission labels, risk-level color coding, summary counts, detail-level toggle, export integration, and unit tests. PermissionEntry record is NOT modified — uses wrapper pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
405 lines
18 KiB
Markdown
405 lines
18 KiB
Markdown
---
|
|
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"
|
|
---
|
|
|
|
<objective>
|
|
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
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@C:/Users/dev/.claude/get-shit-done/workflows/execute-plan.md
|
|
@C:/Users/dev/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
|
|
<interfaces>
|
|
<!-- PermissionEntry is READ-ONLY — do NOT modify this record -->
|
|
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: <name>"
|
|
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<string> FilterPermissionLevels(IEnumerable<string> levels);
|
|
public static bool IsSharingLinksGroup(string loginName);
|
|
}
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Create RiskLevel enum and PermissionLevelMapping helper</name>
|
|
<files>SharepointToolbox/Core/Models/RiskLevel.cs, SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs</files>
|
|
<action>
|
|
Create `SharepointToolbox/Core/Models/RiskLevel.cs`:
|
|
|
|
```csharp
|
|
namespace SharepointToolbox.Core.Models;
|
|
|
|
/// <summary>
|
|
/// Classifies a SharePoint permission level by its access risk.
|
|
/// Used for color coding in both WPF DataGrid and HTML export.
|
|
/// </summary>
|
|
public enum RiskLevel
|
|
{
|
|
/// <summary>Full Control, Site Collection Administrator — can delete site, manage permissions.</summary>
|
|
High,
|
|
/// <summary>Contribute, Edit, Design — can modify content.</summary>
|
|
Medium,
|
|
/// <summary>Read, Restricted View — can view but not modify.</summary>
|
|
Low,
|
|
/// <summary>View Only — most restricted legitimate access.</summary>
|
|
ReadOnly
|
|
}
|
|
```
|
|
|
|
Create `SharepointToolbox/Core/Helpers/PermissionLevelMapping.cs`:
|
|
|
|
```csharp
|
|
using SharepointToolbox.Core.Models;
|
|
|
|
namespace SharepointToolbox.Core.Helpers;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public static class PermissionLevelMapping
|
|
{
|
|
/// <summary>
|
|
/// Result of looking up a SharePoint role name.
|
|
/// </summary>
|
|
public record MappingResult(string Label, RiskLevel RiskLevel);
|
|
|
|
/// <summary>
|
|
/// Known SharePoint built-in permission level mappings.
|
|
/// Keys are case-insensitive via the dictionary comparer.
|
|
/// </summary>
|
|
private static readonly Dictionary<string, MappingResult> 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),
|
|
};
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves a semicolon-delimited PermissionLevels string into individual mapping results.
|
|
/// This handles the PermissionEntry.PermissionLevels format (e.g. "Full Control; Contribute").
|
|
/// </summary>
|
|
public static IReadOnlyList<MappingResult> GetMappings(string permissionLevels)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(permissionLevels))
|
|
return Array.Empty<MappingResult>();
|
|
|
|
return permissionLevels
|
|
.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
|
.Select(GetMapping)
|
|
.ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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"
|
|
/// </summary>
|
|
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
|
|
</action>
|
|
<verify>
|
|
<automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5</automated>
|
|
</verify>
|
|
<done>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.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Create SimplifiedPermissionEntry wrapper and PermissionSummary model</name>
|
|
<files>SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs, SharepointToolbox/Core/Models/PermissionSummary.cs</files>
|
|
<action>
|
|
Create `SharepointToolbox/Core/Models/SimplifiedPermissionEntry.cs`:
|
|
|
|
```csharp
|
|
using SharepointToolbox.Core.Helpers;
|
|
|
|
namespace SharepointToolbox.Core.Models;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public class SimplifiedPermissionEntry
|
|
{
|
|
/// <summary>The original immutable PermissionEntry.</summary>
|
|
public PermissionEntry Inner { get; }
|
|
|
|
/// <summary>
|
|
/// Human-readable labels for the permission levels.
|
|
/// E.g. "Can edit files and list items" instead of "Contribute".
|
|
/// </summary>
|
|
public string SimplifiedLabels { get; }
|
|
|
|
/// <summary>
|
|
/// The highest risk level across all permission levels on this entry.
|
|
/// Used for row-level color coding.
|
|
/// </summary>
|
|
public RiskLevel RiskLevel { get; }
|
|
|
|
/// <summary>
|
|
/// Individual mapping results for each permission level in the entry.
|
|
/// Used when detailed breakdown per-role is needed.
|
|
/// </summary>
|
|
public IReadOnlyList<PermissionLevelMapping.MappingResult> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates SimplifiedPermissionEntry wrappers for a collection of entries.
|
|
/// </summary>
|
|
public static IReadOnlyList<SimplifiedPermissionEntry> WrapAll(
|
|
IEnumerable<PermissionEntry> entries)
|
|
{
|
|
return entries.Select(e => new SimplifiedPermissionEntry(e)).ToList();
|
|
}
|
|
}
|
|
```
|
|
|
|
Create `SharepointToolbox/Core/Models/PermissionSummary.cs`:
|
|
|
|
```csharp
|
|
namespace SharepointToolbox.Core.Models;
|
|
|
|
/// <summary>
|
|
/// Summary counts of permission entries grouped by risk level.
|
|
/// Displayed in the summary panel when simplified mode is active.
|
|
/// </summary>
|
|
public record PermissionSummary(
|
|
/// <summary>Label for this group (e.g. "High Risk", "Read Only").</summary>
|
|
string Label,
|
|
/// <summary>The risk level this group represents.</summary>
|
|
RiskLevel RiskLevel,
|
|
/// <summary>Number of permission entries at this risk level.</summary>
|
|
int Count,
|
|
/// <summary>Number of distinct users at this risk level.</summary>
|
|
int DistinctUsers
|
|
);
|
|
|
|
/// <summary>
|
|
/// Computes PermissionSummary groups from SimplifiedPermissionEntry collections.
|
|
/// </summary>
|
|
public static class PermissionSummaryBuilder
|
|
{
|
|
/// <summary>
|
|
/// Risk level display labels.
|
|
/// </summary>
|
|
private static readonly Dictionary<RiskLevel, string> Labels = new()
|
|
{
|
|
[RiskLevel.High] = "High Risk",
|
|
[RiskLevel.Medium] = "Medium Risk",
|
|
[RiskLevel.Low] = "Low Risk",
|
|
[RiskLevel.ReadOnly] = "Read Only",
|
|
};
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public static IReadOnlyList<PermissionSummary> Build(
|
|
IEnumerable<SimplifiedPermissionEntry> entries)
|
|
{
|
|
var grouped = entries
|
|
.GroupBy(e => e.RiskLevel)
|
|
.ToDictionary(g => g.Key, g => g.ToList());
|
|
|
|
return Enum.GetValues<RiskLevel>()
|
|
.Select(level =>
|
|
{
|
|
var items = grouped.GetValueOrDefault(level, new List<SimplifiedPermissionEntry>());
|
|
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
|
|
</action>
|
|
<verify>
|
|
<automated>cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5</automated>
|
|
</verify>
|
|
<done>SimplifiedPermissionEntry wraps PermissionEntry with SimplifiedLabels, RiskLevel, Mappings, and all passthrough properties. PermissionSummary + PermissionSummaryBuilder provide grouped counts. Project compiles cleanly. PermissionEntry.cs is NOT modified.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `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
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
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.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/08-simplified-permissions/08-01-SUMMARY.md`
|
|
</output>
|