@@ -51,7 +51,10 @@ public class UserAccessCsvExportService
Csv(entry.ObjectUrl),
Csv(entry.PermissionLevel),
Csv(entry.AccessType.ToString()),
- Csv(entry.GrantedThrough)
+ Csv(entry.GrantedThrough),
+ Csv(entry.TargetLabel ?? string.Empty),
+ Csv(entry.TargetUrl ?? string.Empty),
+ Csv(entry.SharingLinkType ?? string.Empty)
}));
}
@@ -179,7 +182,7 @@ public class UserAccessCsvExportService
sb.AppendLine();
// Header
- sb.AppendLine($"\"{T["report.col.user"]}\",\"User Login\",\"{T["report.col.permission_level"]}\",\"{T["report.col.access_type"]}\",\"{T["report.col.granted_through"]}\",\"Locations\",\"Location Count\"");
+ sb.AppendLine($"\"{T["report.col.user"]}\",\"User Login\",\"{T["report.col.permission_level"]}\",\"{T["report.col.access_type"]}\",\"{T["report.col.granted_through"]}\",\"TargetLabel\",\"TargetUrl\",\"SharingLinkType\",\"Locations\",\"Location Count\"");
// Data rows
foreach (var entry in consolidated)
@@ -187,13 +190,16 @@ public class UserAccessCsvExportService
var locations = string.Join("; ", entry.Locations.Select(l => l.SiteTitle));
sb.AppendLine(string.Join(",", new[]
{
- $"\"{entry.UserDisplayName}\"",
- $"\"{entry.UserLogin}\"",
- $"\"{entry.PermissionLevel}\"",
- $"\"{entry.AccessType}\"",
- $"\"{entry.GrantedThrough}\"",
- $"\"{locations}\"",
- $"\"{entry.LocationCount}\""
+ Csv(entry.UserDisplayName),
+ Csv(entry.UserLogin),
+ Csv(entry.PermissionLevel),
+ Csv(entry.AccessType.ToString()),
+ Csv(entry.GrantedThrough),
+ Csv(entry.TargetLabel ?? string.Empty),
+ Csv(entry.TargetUrl ?? string.Empty),
+ Csv(entry.SharingLinkType ?? string.Empty),
+ Csv(locations),
+ Csv(entry.LocationCount.ToString())
}));
}
@@ -226,7 +232,10 @@ public class UserAccessCsvExportService
Csv(entry.ObjectUrl),
Csv(entry.PermissionLevel),
Csv(entry.AccessType.ToString()),
- Csv(entry.GrantedThrough)
+ Csv(entry.GrantedThrough),
+ Csv(entry.TargetLabel ?? string.Empty),
+ Csv(entry.TargetUrl ?? string.Empty),
+ Csv(entry.SharingLinkType ?? string.Empty)
}));
}
diff --git a/SharepointToolbox/Services/Export/UserAccessHtmlExportService.cs b/SharepointToolbox/Services/Export/UserAccessHtmlExportService.cs
index ed941f9..01eda33 100644
--- a/SharepointToolbox/Services/Export/UserAccessHtmlExportService.cs
+++ b/SharepointToolbox/Services/Export/UserAccessHtmlExportService.cs
@@ -250,7 +250,7 @@ a:hover { text-decoration: underline; }
sb.AppendLine($" | {objectCell} | ");
sb.AppendLine($" {HtmlEncode(entry.PermissionLevel)}{highIcon} | ");
sb.AppendLine($" {accessBadge} | ");
- sb.AppendLine($" {HtmlEncode(entry.GrantedThrough)} | ");
+ sb.AppendLine($" {PermissionHtmlFragments.BuildGrantedThroughCell(entry.GrantedThrough, entry.TargetUrl, entry.TargetLabel, entry.SharingLinkType)} | ");
sb.AppendLine("");
}
}
@@ -301,7 +301,7 @@ a:hover { text-decoration: underline; }
sb.AppendLine($" {objectCell} | ");
sb.AppendLine($" {HtmlEncode(entry.PermissionLevel)}{highIcon} | ");
sb.AppendLine($" {accessBadge} | ");
- sb.AppendLine($" {HtmlEncode(entry.GrantedThrough)} | ");
+ sb.AppendLine($" {PermissionHtmlFragments.BuildGrantedThroughCell(entry.GrantedThrough, entry.TargetUrl, entry.TargetLabel, entry.SharingLinkType)} | ");
sb.AppendLine("");
}
}
@@ -579,7 +579,7 @@ a:hover { text-decoration: underline; }
sb.AppendLine($" {HtmlEncode(entry.UserDisplayName)}{guestBadge} | ");
sb.AppendLine($" {HtmlEncode(entry.PermissionLevel)}{highIcon} | ");
sb.AppendLine($" {accessBadge} | ");
- sb.AppendLine($" {HtmlEncode(entry.GrantedThrough)} | ");
+ sb.AppendLine($" {PermissionHtmlFragments.BuildGrantedThroughCell(entry.GrantedThrough, entry.TargetUrl, entry.TargetLabel, entry.SharingLinkType)} | ");
if (entry.LocationCount == 1)
{
diff --git a/SharepointToolbox/Services/ISystemGroupTargetResolver.cs b/SharepointToolbox/Services/ISystemGroupTargetResolver.cs
new file mode 100644
index 0000000..d89275a
--- /dev/null
+++ b/SharepointToolbox/Services/ISystemGroupTargetResolver.cs
@@ -0,0 +1,23 @@
+using Microsoft.SharePoint.Client;
+using SharepointToolbox.Core.Helpers;
+using SharepointToolbox.Core.Models;
+
+namespace SharepointToolbox.Services;
+
+///
+/// Resolves the targeted resource (List, Web, File, Folder) for a SharePoint system
+/// group whose name encodes a GUID — Limited Access System Group For Web/List and
+/// SharingLinks.{itemId}.{type}.{shareId}. Returns null when the target is missing
+/// or unreachable (deleted item, access denied, etc.) — callers fall back to the
+/// raw group name.
+///
+public interface ISystemGroupTargetResolver
+{
+ ///
+ /// Resolves the target for a classified system group. Cached per (ctx site, guid).
+ ///
+ Task ResolveAsync(
+ ClientContext ctx,
+ SystemGroupClassification classification,
+ CancellationToken ct);
+}
diff --git a/SharepointToolbox/Services/PermissionsService.cs b/SharepointToolbox/Services/PermissionsService.cs
index d37f5b2..5069330 100644
--- a/SharepointToolbox/Services/PermissionsService.cs
+++ b/SharepointToolbox/Services/PermissionsService.cs
@@ -11,6 +11,16 @@ namespace SharepointToolbox.Services;
///
public class PermissionsService : IPermissionsService
{
+ private readonly ISystemGroupTargetResolver? _systemGroupResolver;
+
+ public PermissionsService() : this(null) { }
+
+ public PermissionsService(ISystemGroupTargetResolver? systemGroupResolver)
+ {
+ _systemGroupResolver = systemGroupResolver;
+ }
+
+
///