Added new feature : display the file/folder and link of a SharingLink object in the permissions reports.
This commit is contained in:
@@ -50,7 +50,10 @@ public static class PermissionConsolidator
|
||||
first.GrantedThrough,
|
||||
first.IsHighPrivilege,
|
||||
first.IsExternalUser,
|
||||
locations);
|
||||
locations,
|
||||
first.TargetUrl,
|
||||
first.TargetLabel,
|
||||
first.SharingLinkType);
|
||||
})
|
||||
.OrderBy(c => c.UserLogin)
|
||||
.ThenBy(c => c.PermissionLevel)
|
||||
|
||||
@@ -1,10 +1,56 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SharepointToolbox.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Classifies a SharePoint group name into one of the known system-group shapes.
|
||||
/// </summary>
|
||||
public enum SystemGroupKind
|
||||
{
|
||||
/// <summary>Not a system group — a normal SharePoint group, user, or external user.</summary>
|
||||
None,
|
||||
/// <summary>Bare "Limited Access System Group" pseudo-principal (no embedded target).</summary>
|
||||
LimitedAccessBare,
|
||||
/// <summary>"Limited Access System Group For Web {webId}".</summary>
|
||||
LimitedAccessWeb,
|
||||
/// <summary>"Limited Access System Group For List {listId}".</summary>
|
||||
LimitedAccessList,
|
||||
/// <summary>"SharingLinks.{itemUniqueId}.{linkType}.{shareId}".</summary>
|
||||
SharingLink
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of classifying a SharePoint group name.
|
||||
/// Carries the embedded GUIDs / link type so a resolver can look up the target.
|
||||
/// </summary>
|
||||
public readonly record struct SystemGroupClassification(
|
||||
SystemGroupKind Kind,
|
||||
Guid? WebId,
|
||||
Guid? ListId,
|
||||
Guid? ItemUniqueId,
|
||||
string? LinkType,
|
||||
Guid? ShareId);
|
||||
|
||||
/// <summary>
|
||||
/// Pure static helpers for classifying SharePoint permission entries.
|
||||
/// </summary>
|
||||
public static class PermissionEntryHelper
|
||||
{
|
||||
// "Limited Access System Group For Web {guid}"
|
||||
private static readonly Regex LimitedAccessWebRegex = new(
|
||||
@"^Limited Access System Group For Web\s+(?<id>[0-9a-fA-F-]{36})\s*$",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
// "Limited Access System Group For List {guid}"
|
||||
private static readonly Regex LimitedAccessListRegex = new(
|
||||
@"^Limited Access System Group For List\s+(?<id>[0-9a-fA-F-]{36})\s*$",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
// "SharingLinks.{itemUniqueId}.{linkType}.{shareId}"
|
||||
private static readonly Regex SharingLinkRegex = new(
|
||||
@"^SharingLinks\.(?<item>[0-9a-fA-F-]{36})\.(?<type>[^.]+)\.(?<share>[0-9a-fA-F-]{36})\s*$",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the login name is a B2B guest (contains #EXT#).
|
||||
/// </summary>
|
||||
@@ -21,10 +67,45 @@ public static class PermissionEntryHelper
|
||||
.ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true when the login name represents an internal sharing-link group
|
||||
/// or the "Limited Access System Group" pseudo-principal.
|
||||
/// Returns true when the supplied name/login is the bare "Limited Access System Group"
|
||||
/// pseudo-principal — the noise entry with no embedded GUID. The
|
||||
/// per-Web/List and SharingLinks variants are NOT filtered: they are enriched.
|
||||
/// </summary>
|
||||
public static bool IsSharingLinksGroup(string loginName) =>
|
||||
loginName.StartsWith("SharingLinks.", StringComparison.OrdinalIgnoreCase)
|
||||
|| loginName.Equals("Limited Access System Group", StringComparison.OrdinalIgnoreCase);
|
||||
public static bool IsBareLimitedAccessSystemGroup(string name) =>
|
||||
name.Equals("Limited Access System Group", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Classifies a SharePoint group title (the value used in Granted Through). When the
|
||||
/// title matches a known system pattern, the embedded GUIDs / link type are parsed
|
||||
/// out so a resolver can look up the actual targeted Web/List/Item.
|
||||
/// </summary>
|
||||
public static SystemGroupClassification Classify(string groupTitle)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(groupTitle))
|
||||
return new SystemGroupClassification(SystemGroupKind.None, null, null, null, null, null);
|
||||
|
||||
var trimmed = groupTitle.Trim();
|
||||
|
||||
if (IsBareLimitedAccessSystemGroup(trimmed))
|
||||
return new SystemGroupClassification(SystemGroupKind.LimitedAccessBare, null, null, null, null, null);
|
||||
|
||||
var mWeb = LimitedAccessWebRegex.Match(trimmed);
|
||||
if (mWeb.Success && Guid.TryParse(mWeb.Groups["id"].Value, out var webId))
|
||||
return new SystemGroupClassification(SystemGroupKind.LimitedAccessWeb, webId, null, null, null, null);
|
||||
|
||||
var mList = LimitedAccessListRegex.Match(trimmed);
|
||||
if (mList.Success && Guid.TryParse(mList.Groups["id"].Value, out var listId))
|
||||
return new SystemGroupClassification(SystemGroupKind.LimitedAccessList, null, listId, null, null, null);
|
||||
|
||||
var mShare = SharingLinkRegex.Match(trimmed);
|
||||
if (mShare.Success
|
||||
&& Guid.TryParse(mShare.Groups["item"].Value, out var itemId)
|
||||
&& Guid.TryParse(mShare.Groups["share"].Value, out var shareId))
|
||||
{
|
||||
return new SystemGroupClassification(
|
||||
SystemGroupKind.SharingLink, null, null, itemId, mShare.Groups["type"].Value, shareId);
|
||||
}
|
||||
|
||||
return new SystemGroupClassification(SystemGroupKind.None, null, null, null, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
namespace SharepointToolbox.Core.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Risk tier of a SharePoint sharing link, used to color-code the link-type badge
|
||||
/// in HTML reports.
|
||||
/// </summary>
|
||||
public enum SharingLinkRisk
|
||||
{
|
||||
Low, // Read-only, scoped (Org view, Direct, Existing, Review)
|
||||
Medium, // Org-wide edit
|
||||
High, // Anonymous (anyone with link, no auth)
|
||||
Unknown // Unrecognized type — neutral styling
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps the raw <c>linkType</c> segment of a <c>SharingLinks.{itemId}.{type}.{shareId}</c>
|
||||
/// group name to a human-readable label and a risk tier. The raw codes are SharePoint
|
||||
/// internals (OrganizationEdit / AnonymousView / Flexible / …); reports show the friendly
|
||||
/// label and tint the badge by tier.
|
||||
/// </summary>
|
||||
public static class SharingLinkLabels
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the friendly label and risk tier for a sharing-link type code.
|
||||
/// Unknown codes fall through with the raw value and <see cref="SharingLinkRisk.Unknown"/>.
|
||||
/// </summary>
|
||||
public static (string Label, SharingLinkRisk Risk) Describe(string? rawLinkType)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(rawLinkType))
|
||||
return (string.Empty, SharingLinkRisk.Unknown);
|
||||
|
||||
return rawLinkType.Trim() switch
|
||||
{
|
||||
"OrganizationView" => ("Org link · View", SharingLinkRisk.Low),
|
||||
"OrganizationEdit" => ("Org link · Edit", SharingLinkRisk.Medium),
|
||||
"AnonymousView" => ("Anyone · View", SharingLinkRisk.High),
|
||||
"AnonymousEdit" => ("Anyone · Edit", SharingLinkRisk.High),
|
||||
"Flexible" => ("Custom link", SharingLinkRisk.Medium),
|
||||
"Direct" => ("Specific people", SharingLinkRisk.Low),
|
||||
"Existing" => ("Existing access", SharingLinkRisk.Low),
|
||||
"Review" => ("Review only", SharingLinkRisk.Low),
|
||||
"Embed" => ("Embedded link", SharingLinkRisk.Medium),
|
||||
_ => (rawLinkType, SharingLinkRisk.Unknown)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns inline-CSS colors (background, foreground) for a risk tier — matches the
|
||||
/// risk-card palette used elsewhere in the HTML reports.
|
||||
/// </summary>
|
||||
public static (string Background, string Foreground) Colors(SharingLinkRisk risk) => risk switch
|
||||
{
|
||||
SharingLinkRisk.Low => ("#D1FAE5", "#065F46"),
|
||||
SharingLinkRisk.Medium => ("#FEF3C7", "#92400E"),
|
||||
SharingLinkRisk.High => ("#FEE2E2", "#991B1B"),
|
||||
_ => ("#F3F4F6", "#374151"),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user