Files
Sharepoint-Toolbox/.planning/phases/07-user-access-audit/07-01-PLAN.md
Dev 19e4c3852d docs(07): create phase plan - 8 plans across 5 waves
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 12:32:39 +02:00

9.8 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
07-user-access-audit 01 execute 1
SharepointToolbox/Core/Models/UserAccessEntry.cs
SharepointToolbox/Services/IUserAccessAuditService.cs
SharepointToolbox/Services/IGraphUserSearchService.cs
true
UACC-01
UACC-02
truths artifacts key_links
UserAccessEntry record exists with all fields needed for audit results display and export
IUserAccessAuditService interface defines the contract for scanning permissions filtered by user
IGraphUserSearchService interface defines the contract for Graph API people-picker autocomplete
AccessType enum distinguishes Direct, Group, and Inherited access
path provides contains
SharepointToolbox/Core/Models/UserAccessEntry.cs Data model for user-centric audit results record UserAccessEntry
path provides contains
SharepointToolbox/Services/IUserAccessAuditService.cs Service contract for user access auditing interface IUserAccessAuditService
path provides contains
SharepointToolbox/Services/IGraphUserSearchService.cs Service contract for Graph API user search interface IGraphUserSearchService
Define the data models and service interfaces that all subsequent plans depend on. This is the Wave 0 contract layer: UserAccessEntry record, AccessType enum, IUserAccessAuditService, and IGraphUserSearchService.

Purpose: Every other plan in this phase imports these types. Defining them first prevents circular dependencies and gives executors concrete contracts. Output: UserAccessEntry.cs, IUserAccessAuditService.cs, IGraphUserSearchService.cs

<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>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/07-user-access-audit/07-CONTEXT.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 string GrantedThrough, // "Direct Permissions" | "SharePoint Group: " string PrincipalType // "SharePointGroup" | "User" | "External User" );


From SharepointToolbox/Core/Models/SiteInfo.cs:
```csharp
namespace SharepointToolbox.Core.Models;
public record SiteInfo(string Url, string Title);

From SharepointToolbox/Core/Models/ScanOptions.cs (inferred from usage):

public record ScanOptions(bool IncludeInherited, bool ScanFolders, int FolderDepth, bool IncludeSubsites);
Task 1: Create UserAccessEntry model and AccessType enum SharepointToolbox/Core/Models/UserAccessEntry.cs Create `SharepointToolbox/Core/Models/UserAccessEntry.cs` with:
```csharp
namespace SharepointToolbox.Core.Models;

/// <summary>
/// Classifies how a user received a permission assignment.
/// </summary>
public enum AccessType
{
    /// <summary>User is directly assigned a role on the object.</summary>
    Direct,
    /// <summary>User is a member of a SharePoint group that has the role.</summary>
    Group,
    /// <summary>Permission is inherited from a parent object (not unique).</summary>
    Inherited
}

/// <summary>
/// One row in the User Access Audit results grid.
/// Represents a single permission that a specific user holds on a specific object.
/// </summary>
public record UserAccessEntry(
    string UserDisplayName,     // e.g. "Alice Smith"
    string UserLogin,           // e.g. "alice@contoso.com" or "i:0#.f|membership|alice@contoso.com"
    string SiteUrl,             // The site collection URL where this permission exists
    string SiteTitle,           // The site collection title
    string ObjectType,          // "Site Collection" | "Site" | "List" | "Folder"
    string ObjectTitle,         // Name of the list/folder/site
    string ObjectUrl,           // URL of the specific object
    string PermissionLevel,     // e.g. "Full Control", "Contribute"
    AccessType AccessType,      // Direct | Group | Inherited
    string GrantedThrough,      // "Direct Permissions" | "SharePoint Group: Members" etc.
    bool IsHighPrivilege,       // True for Full Control, Site Collection Administrator
    bool IsExternalUser         // True if login contains #EXT#
);
```

Design notes:
- Each row is one user + one object + one permission level (fully denormalized for DataGrid binding)
- IsHighPrivilege pre-computed during scan for warning icon display without re-evaluation
- IsExternalUser pre-computed using PermissionEntryHelper.IsExternalUser pattern
- SiteUrl + SiteTitle included so results can group by site across multi-site scans
cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5 UserAccessEntry.cs and AccessType enum exist in Core/Models/, compile without errors, contain all 12 fields. Task 2: Create IUserAccessAuditService and IGraphUserSearchService interfaces SharepointToolbox/Services/IUserAccessAuditService.cs, SharepointToolbox/Services/IGraphUserSearchService.cs Create `SharepointToolbox/Services/IUserAccessAuditService.cs`:
```csharp
using SharepointToolbox.Core.Models;

namespace SharepointToolbox.Services;

/// <summary>
/// Scans permissions across selected sites and filters results to show
/// only what specific user(s) can access.
/// </summary>
public interface IUserAccessAuditService
{
    /// <summary>
    /// Scans all selected sites for permissions, then filters results to entries
    /// matching the specified user logins. Returns a flat list of UserAccessEntry
    /// records suitable for DataGrid binding and export.
    /// </summary>
    /// <param name="sessionManager">Session manager for creating authenticated contexts.</param>
    /// <param name="targetUserLogins">Login names (emails) of users to audit.</param>
    /// <param name="sites">Sites to scan.</param>
    /// <param name="options">Scan depth options (inherited, folders, subsites).</param>
    /// <param name="progress">Progress reporter.</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>Flat list of access entries for the target users.</returns>
    Task<IReadOnlyList<UserAccessEntry>> AuditUsersAsync(
        ISessionManager sessionManager,
        IReadOnlyList<string> targetUserLogins,
        IReadOnlyList<SiteInfo> sites,
        ScanOptions options,
        IProgress<OperationProgress> progress,
        CancellationToken ct);
}
```

Create `SharepointToolbox/Services/IGraphUserSearchService.cs`:

```csharp
namespace SharepointToolbox.Services;

/// <summary>
/// Searches tenant users via Microsoft Graph API for the people-picker autocomplete.
/// </summary>
public interface IGraphUserSearchService
{
    /// <summary>
    /// Searches for users in the tenant whose display name or email matches the query.
    /// Returns up to <paramref name="maxResults"/> matches.
    /// </summary>
    /// <param name="clientId">The Azure AD app client ID for Graph authentication.</param>
    /// <param name="query">Partial name or email to search for.</param>
    /// <param name="maxResults">Maximum number of results to return (default 10).</param>
    /// <param name="ct">Cancellation token.</param>
    /// <returns>List of (DisplayName, Email/UPN) tuples.</returns>
    Task<IReadOnlyList<GraphUserResult>> SearchUsersAsync(
        string clientId,
        string query,
        int maxResults = 10,
        CancellationToken ct = default);
}

/// <summary>
/// Represents a user returned by the Graph API people search.
/// </summary>
public record GraphUserResult(string DisplayName, string UserPrincipalName, string? Mail);
```
cd "C:\Users\dev\Documents\projets\Sharepoint" && dotnet build SharepointToolbox/SharepointToolbox.csproj --no-incremental 2>&1 | tail -5 Both interface files exist in Services/, compile without errors, IUserAccessAuditService.AuditUsersAsync and IGraphUserSearchService.SearchUsersAsync are defined with correct signatures. - `dotnet build SharepointToolbox/SharepointToolbox.csproj` succeeds with 0 errors - UserAccessEntry.cs contains record with 12 fields and AccessType enum - IUserAccessAuditService.cs contains AuditUsersAsync method signature - IGraphUserSearchService.cs contains SearchUsersAsync method signature and GraphUserResult record

<success_criteria> All three files compile cleanly. The contracts are established: downstream plans (07-02 through 07-08) can import UserAccessEntry, AccessType, IUserAccessAuditService, IGraphUserSearchService, and GraphUserResult without ambiguity. </success_criteria>

After completion, create `.planning/phases/07-user-access-audit/07-01-SUMMARY.md`