---
phase: 07-user-access-audit
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- SharepointToolbox/Core/Models/UserAccessEntry.cs
- SharepointToolbox/Services/IUserAccessAuditService.cs
- SharepointToolbox/Services/IGraphUserSearchService.cs
autonomous: true
requirements:
- UACC-01
- UACC-02
must_haves:
truths:
- "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"
artifacts:
- path: "SharepointToolbox/Core/Models/UserAccessEntry.cs"
provides: "Data model for user-centric audit results"
contains: "record UserAccessEntry"
- path: "SharepointToolbox/Services/IUserAccessAuditService.cs"
provides: "Service contract for user access auditing"
contains: "interface IUserAccessAuditService"
- path: "SharepointToolbox/Services/IGraphUserSearchService.cs"
provides: "Service contract for Graph API user search"
contains: "interface IGraphUserSearchService"
key_links: []
---
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
@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
@.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):
```csharp
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;
///
/// Classifies how a user received a permission assignment.
///
public enum AccessType
{
/// User is directly assigned a role on the object.
Direct,
/// User is a member of a SharePoint group that has the role.
Group,
/// Permission is inherited from a parent object (not unique).
Inherited
}
///
/// One row in the User Access Audit results grid.
/// Represents a single permission that a specific user holds on a specific object.
///
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;
///
/// Scans permissions across selected sites and filters results to show
/// only what specific user(s) can access.
///
public interface IUserAccessAuditService
{
///
/// 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.
///
/// Session manager for creating authenticated contexts.
/// Login names (emails) of users to audit.
/// Sites to scan.
/// Scan depth options (inherited, folders, subsites).
/// Progress reporter.
/// Cancellation token.
/// Flat list of access entries for the target users.
Task> AuditUsersAsync(
ISessionManager sessionManager,
IReadOnlyList targetUserLogins,
IReadOnlyList sites,
ScanOptions options,
IProgress progress,
CancellationToken ct);
}
```
Create `SharepointToolbox/Services/IGraphUserSearchService.cs`:
```csharp
namespace SharepointToolbox.Services;
///
/// Searches tenant users via Microsoft Graph API for the people-picker autocomplete.
///
public interface IGraphUserSearchService
{
///
/// Searches for users in the tenant whose display name or email matches the query.
/// Returns up to matches.
///
/// The Azure AD app client ID for Graph authentication.
/// Partial name or email to search for.
/// Maximum number of results to return (default 10).
/// Cancellation token.
/// List of (DisplayName, Email/UPN) tuples.
Task> SearchUsersAsync(
string clientId,
string query,
int maxResults = 10,
CancellationToken ct = default);
}
///
/// Represents a user returned by the Graph API people search.
///
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
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.