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:
@@ -27,6 +27,8 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
|
||||
private readonly UserAccessHtmlExportService? _htmlExportService;
|
||||
private readonly IBrandingService? _brandingService;
|
||||
private readonly IGraphUserDirectoryService? _graphUserDirectoryService;
|
||||
private readonly IOwnershipElevationService? _ownershipService;
|
||||
private readonly SettingsService? _settingsService;
|
||||
private readonly ILogger<FeatureViewModelBase> _logger;
|
||||
|
||||
// ── People picker debounce ──────────────────────────────────────────────
|
||||
@@ -163,7 +165,9 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
|
||||
UserAccessHtmlExportService htmlExportService,
|
||||
IBrandingService brandingService,
|
||||
IGraphUserDirectoryService graphUserDirectoryService,
|
||||
ILogger<FeatureViewModelBase> logger)
|
||||
ILogger<FeatureViewModelBase> logger,
|
||||
IOwnershipElevationService? ownershipService = null,
|
||||
SettingsService? settingsService = null)
|
||||
: base(logger)
|
||||
{
|
||||
_auditService = auditService;
|
||||
@@ -173,6 +177,8 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
|
||||
_htmlExportService = htmlExportService;
|
||||
_brandingService = brandingService;
|
||||
_graphUserDirectoryService = graphUserDirectoryService;
|
||||
_ownershipService = ownershipService;
|
||||
_settingsService = settingsService;
|
||||
_logger = logger;
|
||||
|
||||
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, CanExport);
|
||||
@@ -273,6 +279,35 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
|
||||
return;
|
||||
}
|
||||
|
||||
var autoOwnership = await IsAutoTakeOwnershipEnabled();
|
||||
|
||||
Func<string, CancellationToken, Task<bool>>? onAccessDenied = null;
|
||||
if (_ownershipService != null && autoOwnership)
|
||||
{
|
||||
onAccessDenied = async (siteUrl, token) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogWarning("Access denied on {Url}, auto-elevating ownership...", siteUrl);
|
||||
var adminUrl = DeriveAdminUrl(_currentProfile?.TenantUrl ?? siteUrl);
|
||||
var adminProfile = new TenantProfile
|
||||
{
|
||||
TenantUrl = adminUrl,
|
||||
ClientId = _currentProfile?.ClientId ?? string.Empty,
|
||||
Name = _currentProfile?.Name ?? string.Empty
|
||||
};
|
||||
var adminCtx = await _sessionManager.GetOrCreateContextAsync(adminProfile, token);
|
||||
await _ownershipService.ElevateAsync(adminCtx, siteUrl, string.Empty, token);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Auto-elevation failed for {Url}", siteUrl);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var entries = await _auditService.AuditUsersAsync(
|
||||
_sessionManager,
|
||||
_currentProfile,
|
||||
@@ -280,7 +315,8 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
|
||||
effectiveSites,
|
||||
scanOptions,
|
||||
progress,
|
||||
ct);
|
||||
ct,
|
||||
onAccessDenied);
|
||||
|
||||
// Update Results on the UI thread — clear + repopulate (not replace)
|
||||
// so the CollectionViewSource bound to ResultsView stays connected.
|
||||
@@ -307,6 +343,26 @@ public partial class UserAccessAuditViewModel : FeatureViewModelBase
|
||||
ExportHtmlCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
// ── Auto-ownership helpers ───────────────────────────────────────────────
|
||||
|
||||
private async Task<bool> IsAutoTakeOwnershipEnabled()
|
||||
{
|
||||
if (_settingsService == null) return false;
|
||||
var settings = await _settingsService.GetSettingsAsync();
|
||||
return settings.AutoTakeOwnership;
|
||||
}
|
||||
|
||||
internal static string DeriveAdminUrl(string tenantUrl)
|
||||
{
|
||||
var uri = new Uri(tenantUrl.TrimEnd('/'));
|
||||
var host = uri.Host;
|
||||
if (host.Contains("-admin.sharepoint.com", StringComparison.OrdinalIgnoreCase))
|
||||
return tenantUrl;
|
||||
var adminHost = host.Replace(".sharepoint.com", "-admin.sharepoint.com",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
return $"{uri.Scheme}://{adminHost}";
|
||||
}
|
||||
|
||||
// ── Tenant switching ─────────────────────────────────────────────────────
|
||||
|
||||
protected override void OnTenantSwitched(TenantProfile profile)
|
||||
|
||||
Reference in New Issue
Block a user