feat(18-01): models, SettingsService, OwnershipElevationService + tests

- AppSettings.AutoTakeOwnership bool property defaulting to false
- PermissionEntry.WasAutoElevated optional param (default false, last position)
- SettingsService.SetAutoTakeOwnershipAsync persists toggle
- IOwnershipElevationService interface + OwnershipElevationService wrapping Tenant.SetSiteAdmin
- SettingsViewModel.AutoTakeOwnership property loads and persists via SetAutoTakeOwnershipAsync
- DI registration in App.xaml.cs (Phase 18 section)
- 8 new tests: models, persistence, service, viewmodel
This commit is contained in:
Dev
2026-04-09 14:23:08 +02:00
parent 3479fff4c3
commit 36fb312b5a
9 changed files with 183 additions and 1 deletions

View File

@@ -161,6 +161,9 @@ public partial class App : Application
// Phase 17: Group Expansion
services.AddTransient<ISharePointGroupResolver, SharePointGroupResolver>();
// Phase 18: Auto-Take Ownership
services.AddTransient<IOwnershipElevationService, OwnershipElevationService>();
// Phase 7: User Access Audit
services.AddTransient<IUserAccessAuditService, UserAccessAuditService>();
services.AddTransient<IGraphUserSearchService, GraphUserSearchService>();

View File

@@ -4,4 +4,5 @@ public class AppSettings
{
public string DataFolder { get; set; } = string.Empty;
public string Lang { get; set; } = "en";
public bool AutoTakeOwnership { get; set; } = false;
}

View File

@@ -13,5 +13,6 @@ public record PermissionEntry(
string UserLogins, // Semicolon-joined login names
string PermissionLevels, // Semicolon-joined role names (Limited Access already removed)
string GrantedThrough, // "Direct Permissions" | "SharePoint Group: <name>"
string PrincipalType // "SharePointGroup" | "User" | "External User"
string PrincipalType, // "SharePointGroup" | "User" | "External User"
bool WasAutoElevated = false // Set to true when site admin was auto-granted to access this entry
);

View File

@@ -0,0 +1,8 @@
using Microsoft.SharePoint.Client;
namespace SharepointToolbox.Services;
public interface IOwnershipElevationService
{
Task ElevateAsync(ClientContext tenantAdminCtx, string siteUrl, string loginName, CancellationToken ct);
}

View File

@@ -0,0 +1,14 @@
using Microsoft.Online.SharePoint.TenantAdministration;
using Microsoft.SharePoint.Client;
namespace SharepointToolbox.Services;
public class OwnershipElevationService : IOwnershipElevationService
{
public async Task ElevateAsync(ClientContext tenantAdminCtx, string siteUrl, string loginName, CancellationToken ct)
{
var tenant = new Tenant(tenantAdminCtx);
tenant.SetSiteAdmin(siteUrl, loginName, isSiteAdmin: true);
await tenantAdminCtx.ExecuteQueryAsync();
}
}

View File

@@ -36,4 +36,11 @@ public class SettingsService
settings.DataFolder = path;
await _repository.SaveAsync(settings);
}
public async Task SetAutoTakeOwnershipAsync(bool enabled)
{
var settings = await _repository.LoadAsync();
settings.AutoTakeOwnership = enabled;
await _repository.SaveAsync(settings);
}
}

View File

@@ -39,6 +39,19 @@ public partial class SettingsViewModel : FeatureViewModelBase
}
}
private bool _autoTakeOwnership;
public bool AutoTakeOwnership
{
get => _autoTakeOwnership;
set
{
if (_autoTakeOwnership == value) return;
_autoTakeOwnership = value;
OnPropertyChanged();
_ = _settingsService.SetAutoTakeOwnershipAsync(value);
}
}
private string? _mspLogoPreview;
public string? MspLogoPreview
{
@@ -65,8 +78,10 @@ public partial class SettingsViewModel : FeatureViewModelBase
var settings = await _settingsService.GetSettingsAsync();
_selectedLanguage = settings.Lang;
_dataFolder = settings.DataFolder;
_autoTakeOwnership = settings.AutoTakeOwnership;
OnPropertyChanged(nameof(SelectedLanguage));
OnPropertyChanged(nameof(DataFolder));
OnPropertyChanged(nameof(AutoTakeOwnership));
var mspLogo = await _brandingService.GetMspLogoAsync();
MspLogoPreview = mspLogo is not null ? $"data:{mspLogo.MimeType};base64,{mspLogo.Base64}" : null;