chore: release v2.4
- Add theme system (Dark/Light palettes, ModernTheme, ThemeManager) - Add InputDialog, Spinner common view - Add DuplicatesCsvExportService - Refresh views, dialogs, and view models across tabs - Update localization strings (en/fr) - Tweak services (transfer, permissions, search, user access, ownership elevation, bulk operations) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Microsoft.SharePoint.Client;
|
||||
using Serilog;
|
||||
using SharepointToolbox.Core.Helpers;
|
||||
using SharepointToolbox.Core.Models;
|
||||
|
||||
@@ -10,6 +11,21 @@ namespace SharepointToolbox.Services;
|
||||
/// </summary>
|
||||
public class PermissionsService : IPermissionsService
|
||||
{
|
||||
/// <summary>
|
||||
/// Detects the SharePoint server error raised when a RoleAssignment member
|
||||
/// refers to a user that no longer resolves (orphaned Azure AD account).
|
||||
/// Message surfaces in the user's locale — match on language-agnostic tokens.
|
||||
/// </summary>
|
||||
private static bool IsClaimsResolutionError(ServerException ex)
|
||||
{
|
||||
var msg = ex.Message ?? string.Empty;
|
||||
return msg.Contains("Claims", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("Revendications", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("Org ID", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("ID org", StringComparison.OrdinalIgnoreCase)
|
||||
|| msg.Contains("OrgIdToClaims", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// Port of PS lines 1914-1926: system lists excluded from permission reporting
|
||||
private static readonly HashSet<string> ExcludedLists = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
@@ -122,7 +138,17 @@ public class PermissionsService : IPermissionsService
|
||||
u => u.Title,
|
||||
u => u.LoginName,
|
||||
u => u.IsSiteAdmin));
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);
|
||||
|
||||
try
|
||||
{
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);
|
||||
}
|
||||
catch (ServerException ex) when (IsClaimsResolutionError(ex))
|
||||
{
|
||||
Log.Warning("Skipped site collection admins for {Url} — orphaned user: {Error}",
|
||||
ctx.Web.Url, ex.Message);
|
||||
return Enumerable.Empty<PermissionEntry>();
|
||||
}
|
||||
|
||||
var admins = ctx.Web.SiteUsers
|
||||
.Where(u => u.IsSiteAdmin)
|
||||
@@ -280,7 +306,23 @@ public class PermissionsService : IPermissionsService
|
||||
ra => ra.Member.LoginName,
|
||||
ra => ra.Member.PrincipalType,
|
||||
ra => ra.RoleDefinitionBindings.Include(rdb => rdb.Name)));
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);
|
||||
|
||||
// Orphaned AD users in RoleAssignments cause the server to throw
|
||||
// "Cannot convert Org ID user to Claims user" during claim resolution.
|
||||
// That kills the whole batch — skip this object so the scan continues.
|
||||
// Only swallow the claims-resolution signature; real access-denied errors
|
||||
// must bubble up so callers (e.g. PermissionsViewModel auto-elevation)
|
||||
// can react to them.
|
||||
try
|
||||
{
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct);
|
||||
}
|
||||
catch (ServerException ex) when (IsClaimsResolutionError(ex))
|
||||
{
|
||||
Log.Warning("Skipped {Type} '{Title}' ({Url}) — orphaned user in permissions: {Error}",
|
||||
objectType, title, url, ex.Message);
|
||||
return Enumerable.Empty<PermissionEntry>();
|
||||
}
|
||||
|
||||
// Skip inherited objects when IncludeInherited=false
|
||||
if (!options.IncludeInherited && !obj.HasUniqueRoleAssignments)
|
||||
|
||||
Reference in New Issue
Block a user