diff --git a/README.md b/README.md index 7f2ce8c..c2d0f5c 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,4 @@ Application pour administrer, auditer et exporter des donnees depuis des sites S ## Architecture -<<<<<<< HEAD MVVM (CommunityToolkit) · DI via Microsoft.Extensions.Hosting · Authentification MSAL avec cache persistant et broker WAM · Microsoft Graph SDK · PnP.Framework (CSOM) · Localisation .resx (EN/FR) · Branding configurable dans les exports HTML. -======= -MVVM (CommunityToolkit) · DI via Microsoft.Extensions.Hosting · Authentification MSAL avec cache persistant et broker WAM · Microsoft Graph SDK · PnP.Framework (CSOM) · Localisation .resx (EN/FR) · Branding configurable dans les exports HTML. ->>>>>>> bbfb9097ce51d03430cb030531418f8b944e9a76 diff --git a/SharepointToolbox/App.xaml.cs b/SharepointToolbox/App.xaml.cs index 3ea3491..a41a869 100644 --- a/SharepointToolbox/App.xaml.cs +++ b/SharepointToolbox/App.xaml.cs @@ -34,6 +34,19 @@ public partial class App : Application .Build(); host.Start(); + + // Apply persisted language before any UI is created so bindings resolve to the saved culture. + try + { + var settings = host.Services.GetRequiredService().GetSettingsAsync().GetAwaiter().GetResult(); + if (!string.IsNullOrWhiteSpace(settings.Lang)) + Localization.TranslationSource.Instance.CurrentCulture = new System.Globalization.CultureInfo(settings.Lang); + } + catch (Exception ex) + { + Log.Warning(ex, "Failed to apply persisted language at startup"); + } + App app = new(); app.InitializeComponent(); diff --git a/SharepointToolbox/Localization/Strings.fr.resx b/SharepointToolbox/Localization/Strings.fr.resx index 428a59f..5f39a1d 100644 --- a/SharepointToolbox/Localization/Strings.fr.resx +++ b/SharepointToolbox/Localization/Strings.fr.resx @@ -124,6 +124,9 @@ ID client + + Optionnel — laissez vide pour enregistrer l'application automatiquement + Ajouter diff --git a/SharepointToolbox/Localization/Strings.resx b/SharepointToolbox/Localization/Strings.resx index 883b62a..14a3075 100644 --- a/SharepointToolbox/Localization/Strings.resx +++ b/SharepointToolbox/Localization/Strings.resx @@ -124,6 +124,9 @@ Client ID + + Optional — leave blank to register the app automatically + Add diff --git a/SharepointToolbox/Services/ProfileService.cs b/SharepointToolbox/Services/ProfileService.cs index 5cf5b32..574cbac 100644 --- a/SharepointToolbox/Services/ProfileService.cs +++ b/SharepointToolbox/Services/ProfileService.cs @@ -24,8 +24,9 @@ public class ProfileService !Uri.TryCreate(profile.TenantUrl, UriKind.Absolute, out _)) throw new ArgumentException("TenantUrl must be a valid absolute URL.", nameof(profile)); - if (string.IsNullOrWhiteSpace(profile.ClientId)) - throw new ArgumentException("ClientId must not be empty.", nameof(profile)); + // ClientId is optional at creation time: the user can register the app from within + // the tool, which will populate ClientId/AppId on the profile afterwards. + profile.ClientId ??= string.Empty; var existing = (await _repository.LoadAsync()).ToList(); existing.Add(profile); diff --git a/SharepointToolbox/ViewModels/ProfileManagementViewModel.cs b/SharepointToolbox/ViewModels/ProfileManagementViewModel.cs index 8b29d1b..78c32aa 100644 --- a/SharepointToolbox/ViewModels/ProfileManagementViewModel.cs +++ b/SharepointToolbox/ViewModels/ProfileManagementViewModel.cs @@ -19,6 +19,11 @@ public partial class ProfileManagementViewModel : ObservableObject private readonly ILogger _logger; private readonly IAppRegistrationService _appRegistrationService; + // Well-known public client (Microsoft Graph Command Line Tools) used as a bootstrap + // when a profile has no ClientId yet, so the user can sign in as admin and have the + // app registration created for them. + private const string BootstrapClientId = "14d82eec-204b-4c2f-b7e8-296a70dab67e"; + [ObservableProperty] private TenantProfile? _selectedProfile; @@ -137,7 +142,7 @@ public partial class ProfileManagementViewModel : ObservableObject { if (string.IsNullOrWhiteSpace(NewName)) return false; if (!Uri.TryCreate(NewTenantUrl, UriKind.Absolute, out _)) return false; - if (string.IsNullOrWhiteSpace(NewClientId)) return false; + // ClientId is optional — leaving it blank lets the user register the app from within the tool. return true; } @@ -150,7 +155,7 @@ public partial class ProfileManagementViewModel : ObservableObject { Name = NewName.Trim(), TenantUrl = NewTenantUrl.Trim(), - ClientId = NewClientId.Trim() + ClientId = NewClientId?.Trim() ?? string.Empty }; await _profileService.AddProfileAsync(profile); Profiles.Add(profile); @@ -299,7 +304,14 @@ public partial class ProfileManagementViewModel : ObservableObject RegistrationStatus = TranslationSource.Instance["profile.register.checking"]; try { - var isAdmin = await _appRegistrationService.IsGlobalAdminAsync(SelectedProfile.ClientId, ct); + // Use the profile's own ClientId if it has one; otherwise bootstrap with the + // Microsoft Graph Command Line Tools public client so a first-time profile + // (name + URL only) can still perform the admin check and registration. + var authClientId = string.IsNullOrWhiteSpace(SelectedProfile.ClientId) + ? BootstrapClientId + : SelectedProfile.ClientId; + + var isAdmin = await _appRegistrationService.IsGlobalAdminAsync(authClientId, ct); if (!isAdmin) { ShowFallbackInstructions = true; @@ -308,11 +320,15 @@ public partial class ProfileManagementViewModel : ObservableObject } RegistrationStatus = TranslationSource.Instance["profile.register.registering"]; - var result = await _appRegistrationService.RegisterAsync(SelectedProfile.ClientId, SelectedProfile.Name, ct); + var result = await _appRegistrationService.RegisterAsync(authClientId, SelectedProfile.Name, ct); if (result.IsSuccess) { SelectedProfile.AppId = result.AppId; + // If the profile had no ClientId, adopt the freshly registered app's id + // so subsequent sign-ins use the profile's own app registration. + if (string.IsNullOrWhiteSpace(SelectedProfile.ClientId)) + SelectedProfile.ClientId = result.AppId!; await _profileService.UpdateProfileAsync(SelectedProfile); RegistrationStatus = TranslationSource.Instance["profile.register.success"]; OnPropertyChanged(nameof(HasRegisteredApp)); diff --git a/SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml b/SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml index aca0801..567a763 100644 --- a/SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml +++ b/SharepointToolbox/Views/Dialogs/ProfileManagementDialog.xaml @@ -35,6 +35,7 @@ +