using System.IO; using System.Text; using SharepointToolbox.Core.Models; namespace SharepointToolbox.Services.Export; /// /// Exports user access audit results to CSV format. /// Produces one CSV file per audited user with a summary section at the top. /// public class UserAccessCsvExportService { private const string DataHeader = "\"Site\",\"Object Type\",\"Object\",\"URL\",\"Permission Level\",\"Access Type\",\"Granted Through\""; /// /// Builds a CSV string for a single user's access entries. /// Includes a summary section at the top followed by data rows. /// public string BuildCsv(string userDisplayName, string userLogin, IReadOnlyList entries) { var sb = new StringBuilder(); // Summary section var sitesCount = entries.Select(e => e.SiteUrl).Distinct().Count(); var highPrivCount = entries.Count(e => e.IsHighPrivilege); sb.AppendLine($"\"User Access Audit Report\""); sb.AppendLine($"\"User\",\"{Csv(userDisplayName)} ({Csv(userLogin)})\""); sb.AppendLine($"\"Total Accesses\",\"{entries.Count}\""); sb.AppendLine($"\"Sites\",\"{sitesCount}\""); sb.AppendLine($"\"High Privilege\",\"{highPrivCount}\""); sb.AppendLine($"\"Generated\",\"{DateTime.Now:yyyy-MM-dd HH:mm:ss}\""); sb.AppendLine(); // Blank line separating summary from data // Data rows sb.AppendLine(DataHeader); foreach (var entry in entries) { sb.AppendLine(string.Join(",", new[] { Csv(entry.SiteTitle), Csv(entry.ObjectType), Csv(entry.ObjectTitle), Csv(entry.ObjectUrl), Csv(entry.PermissionLevel), Csv(entry.AccessType.ToString()), Csv(entry.GrantedThrough) })); } return sb.ToString(); } /// /// Writes one CSV file per user to the specified directory. /// File names: audit_{email}_{date}.csv /// public async Task WriteAsync( IReadOnlyList allEntries, string directoryPath, CancellationToken ct) { Directory.CreateDirectory(directoryPath); var dateStr = DateTime.Now.ToString("yyyy-MM-dd"); // Group by user var byUser = allEntries.GroupBy(e => e.UserLogin); foreach (var group in byUser) { ct.ThrowIfCancellationRequested(); var userLogin = group.Key; var displayName = group.First().UserDisplayName; var entries = group.ToList(); // Sanitize email for filename (replace @ and other invalid chars) var safeLogin = SanitizeFileName(userLogin); var fileName = $"audit_{safeLogin}_{dateStr}.csv"; var filePath = Path.Combine(directoryPath, fileName); var csv = BuildCsv(displayName, userLogin, entries); await File.WriteAllTextAsync(filePath, csv, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true), ct); } } /// /// Writes all entries to a single CSV file (alternative for single-file export). /// Used when the ViewModel export command picks a single file path. /// public async Task WriteSingleFileAsync( IReadOnlyList entries, string filePath, CancellationToken ct) { var sb = new StringBuilder(); var fullHeader = "\"User\",\"User Login\"," + DataHeader; // Summary var users = entries.Select(e => e.UserLogin).Distinct().ToList(); sb.AppendLine($"\"User Access Audit Report\""); sb.AppendLine($"\"Users Audited\",\"{users.Count}\""); sb.AppendLine($"\"Total Accesses\",\"{entries.Count}\""); sb.AppendLine($"\"Generated\",\"{DateTime.Now:yyyy-MM-dd HH:mm:ss}\""); sb.AppendLine(); sb.AppendLine(fullHeader); foreach (var entry in entries) { sb.AppendLine(string.Join(",", new[] { Csv(entry.UserDisplayName), Csv(entry.UserLogin), Csv(entry.SiteTitle), Csv(entry.ObjectType), Csv(entry.ObjectTitle), Csv(entry.ObjectUrl), Csv(entry.PermissionLevel), Csv(entry.AccessType.ToString()), Csv(entry.GrantedThrough) })); } await File.WriteAllTextAsync(filePath, sb.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: true), ct); } /// RFC 4180 CSV field escaping: wrap in double quotes, double internal quotes. private static string Csv(string value) { if (string.IsNullOrEmpty(value)) return "\"\""; return $"\"{value.Replace("\"", "\"\"")}\""; } private static string SanitizeFileName(string name) { var invalid = Path.GetInvalidFileNameChars(); var sb = new StringBuilder(name.Length); foreach (var c in name) sb.Append(invalid.Contains(c) ? '_' : c); return sb.ToString(); } }