Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f30a60d2a | ||
| 6e05d26114 |
@@ -34,6 +34,19 @@ public partial class App : Application
|
|||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
host.Start();
|
host.Start();
|
||||||
|
|
||||||
|
// Apply persisted language before any UI is created so bindings resolve to the saved culture.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = host.Services.GetRequiredService<SettingsService>().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 app = new();
|
||||||
app.InitializeComponent();
|
app.InitializeComponent();
|
||||||
|
|
||||||
|
|||||||
@@ -124,6 +124,9 @@
|
|||||||
<data name="profile.clientid" xml:space="preserve">
|
<data name="profile.clientid" xml:space="preserve">
|
||||||
<value>ID client</value>
|
<value>ID client</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="profile.clientid.hint" xml:space="preserve">
|
||||||
|
<value>Optionnel — laissez vide pour enregistrer l'application automatiquement</value>
|
||||||
|
</data>
|
||||||
<data name="profile.add" xml:space="preserve">
|
<data name="profile.add" xml:space="preserve">
|
||||||
<value>Ajouter</value>
|
<value>Ajouter</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -124,6 +124,9 @@
|
|||||||
<data name="profile.clientid" xml:space="preserve">
|
<data name="profile.clientid" xml:space="preserve">
|
||||||
<value>Client ID</value>
|
<value>Client ID</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="profile.clientid.hint" xml:space="preserve">
|
||||||
|
<value>Optional — leave blank to register the app automatically</value>
|
||||||
|
</data>
|
||||||
<data name="profile.add" xml:space="preserve">
|
<data name="profile.add" xml:space="preserve">
|
||||||
<value>Add</value>
|
<value>Add</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ public class ProfileService
|
|||||||
!Uri.TryCreate(profile.TenantUrl, UriKind.Absolute, out _))
|
!Uri.TryCreate(profile.TenantUrl, UriKind.Absolute, out _))
|
||||||
throw new ArgumentException("TenantUrl must be a valid absolute URL.", nameof(profile));
|
throw new ArgumentException("TenantUrl must be a valid absolute URL.", nameof(profile));
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(profile.ClientId))
|
// ClientId is optional at creation time: the user can register the app from within
|
||||||
throw new ArgumentException("ClientId must not be empty.", nameof(profile));
|
// the tool, which will populate ClientId/AppId on the profile afterwards.
|
||||||
|
profile.ClientId ??= string.Empty;
|
||||||
|
|
||||||
var existing = (await _repository.LoadAsync()).ToList();
|
var existing = (await _repository.LoadAsync()).ToList();
|
||||||
existing.Add(profile);
|
existing.Add(profile);
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ public partial class ProfileManagementViewModel : ObservableObject
|
|||||||
private readonly ILogger<ProfileManagementViewModel> _logger;
|
private readonly ILogger<ProfileManagementViewModel> _logger;
|
||||||
private readonly IAppRegistrationService _appRegistrationService;
|
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]
|
[ObservableProperty]
|
||||||
private TenantProfile? _selectedProfile;
|
private TenantProfile? _selectedProfile;
|
||||||
|
|
||||||
@@ -137,7 +142,7 @@ public partial class ProfileManagementViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(NewName)) return false;
|
if (string.IsNullOrWhiteSpace(NewName)) return false;
|
||||||
if (!Uri.TryCreate(NewTenantUrl, UriKind.Absolute, out _)) 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +155,7 @@ public partial class ProfileManagementViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
Name = NewName.Trim(),
|
Name = NewName.Trim(),
|
||||||
TenantUrl = NewTenantUrl.Trim(),
|
TenantUrl = NewTenantUrl.Trim(),
|
||||||
ClientId = NewClientId.Trim()
|
ClientId = NewClientId?.Trim() ?? string.Empty
|
||||||
};
|
};
|
||||||
await _profileService.AddProfileAsync(profile);
|
await _profileService.AddProfileAsync(profile);
|
||||||
Profiles.Add(profile);
|
Profiles.Add(profile);
|
||||||
@@ -299,7 +304,14 @@ public partial class ProfileManagementViewModel : ObservableObject
|
|||||||
RegistrationStatus = TranslationSource.Instance["profile.register.checking"];
|
RegistrationStatus = TranslationSource.Instance["profile.register.checking"];
|
||||||
try
|
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)
|
if (!isAdmin)
|
||||||
{
|
{
|
||||||
ShowFallbackInstructions = true;
|
ShowFallbackInstructions = true;
|
||||||
@@ -308,11 +320,15 @@ public partial class ProfileManagementViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
RegistrationStatus = TranslationSource.Instance["profile.register.registering"];
|
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)
|
if (result.IsSuccess)
|
||||||
{
|
{
|
||||||
SelectedProfile.AppId = result.AppId;
|
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);
|
await _profileService.UpdateProfileAsync(SelectedProfile);
|
||||||
RegistrationStatus = TranslationSource.Instance["profile.register.success"];
|
RegistrationStatus = TranslationSource.Instance["profile.register.success"];
|
||||||
OnPropertyChanged(nameof(HasRegisteredApp));
|
OnPropertyChanged(nameof(HasRegisteredApp));
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.name]}"
|
<Label Content="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.name]}"
|
||||||
Grid.Row="0" Grid.Column="0" />
|
Grid.Row="0" Grid.Column="0" />
|
||||||
@@ -48,6 +49,9 @@
|
|||||||
Grid.Row="2" Grid.Column="0" />
|
Grid.Row="2" Grid.Column="0" />
|
||||||
<TextBox Text="{Binding NewClientId, UpdateSourceTrigger=PropertyChanged}"
|
<TextBox Text="{Binding NewClientId, UpdateSourceTrigger=PropertyChanged}"
|
||||||
Grid.Row="2" Grid.Column="1" Margin="0,2" />
|
Grid.Row="2" Grid.Column="1" Margin="0,2" />
|
||||||
|
<TextBlock Grid.Row="3" Grid.Column="1" Margin="0,2,0,0"
|
||||||
|
FontSize="11" FontStyle="Italic" Foreground="#666666" TextWrapping="Wrap"
|
||||||
|
Text="{Binding Source={x:Static loc:TranslationSource.Instance}, Path=[profile.clientid.hint]}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Client Logo -->
|
<!-- Client Logo -->
|
||||||
|
|||||||
Reference in New Issue
Block a user