diff --git a/KeyVaultExplorer/Models/AppSettings.cs b/KeyVaultExplorer/Models/AppSettings.cs index def03b0..95fb3ee 100644 --- a/KeyVaultExplorer/Models/AppSettings.cs +++ b/KeyVaultExplorer/Models/AppSettings.cs @@ -16,7 +16,7 @@ public class AppSettings public string PanePlacement { get; set; } = "Left"; public bool SettingsPageClientIdCheckbox { get; set; } = false; public string CustomClientId { get; set; } = string.Empty; - - - + + [AllowedValues("Public", "USGovernment")] + public string AzureCloud { get; set; } = "Public"; } \ No newline at end of file diff --git a/KeyVaultExplorer/Models/CloudEnvironment.cs b/KeyVaultExplorer/Models/CloudEnvironment.cs new file mode 100644 index 0000000..8689011 --- /dev/null +++ b/KeyVaultExplorer/Models/CloudEnvironment.cs @@ -0,0 +1,49 @@ +namespace KeyVaultExplorer.Models; + +public enum AzureCloud +{ + Public, + USGovernment +} + +public class CloudEnvironment +{ + public AzureCloud Cloud { get; set; } + public string Name { get; set; } + public string Authority { get; set; } + public string VaultScope { get; set; } + public string ManagementScope { get; set; } + public string PortalUrl { get; set; } + + public static readonly CloudEnvironment Public = new() + { + Cloud = AzureCloud.Public, + Name = "Azure Public Cloud", + Authority = "https://login.microsoftonline.com", + VaultScope = "https://vault.azure.net/.default", + ManagementScope = "https://management.core.windows.net/.default", + PortalUrl = "https://portal.azure.com" + }; + + public static readonly CloudEnvironment USGovernment = new() + { + Cloud = AzureCloud.USGovernment, + Name = "Azure US Government", + Authority = "https://login.microsoftonline.us", + VaultScope = "https://vault.usgovcloudapi.net/.default", + ManagementScope = "https://management.usgovcloudapi.net/.default", + PortalUrl = "https://portal.azure.us" + }; + + public static CloudEnvironment GetCloudEnvironment(AzureCloud cloud) + { + return cloud switch + { + AzureCloud.Public => Public, + AzureCloud.USGovernment => USGovernment, + _ => Public + }; + } + + public static CloudEnvironment[] AllEnvironments => [Public, USGovernment]; +} \ No newline at end of file diff --git a/KeyVaultExplorer/Models/Constants.cs b/KeyVaultExplorer/Models/Constants.cs index 7486c5e..cf29b84 100644 --- a/KeyVaultExplorer/Models/Constants.cs +++ b/KeyVaultExplorer/Models/Constants.cs @@ -20,11 +20,28 @@ public static class Constants //Leaving the scope to its default values. public static readonly string[] Scopes = ["openid", "offline_access", "profile", "email",]; + // Legacy constants for backwards compatibility - use CloudEnvironment instead public static readonly string[] AzureRMScope = ["https://management.core.windows.net//.default"]; public static readonly string[] KvScope = ["https://vault.azure.net/.default"]; public static readonly string[] AzureScopes = ["https://management.core.windows.net//.default", "https://vault.azure.net//.default", "user_impersonation"]; + + // Helper methods to get cloud-specific scopes + public static string[] GetKeyVaultScope(CloudEnvironment cloudEnvironment) + { + return [cloudEnvironment.VaultScope]; + } + + public static string[] GetAzureResourceManagerScope(CloudEnvironment cloudEnvironment) + { + return [cloudEnvironment.ManagementScope]; + } + + public static string[] GetAzureScopes(CloudEnvironment cloudEnvironment) + { + return [cloudEnvironment.ManagementScope, cloudEnvironment.VaultScope, "user_impersonation"]; + } // Cache settings public const string CacheFileName = "keyvaultexplorer_msal_cache.txt"; diff --git a/KeyVaultExplorer/Services/AuthService.cs b/KeyVaultExplorer/Services/AuthService.cs index de6d643..6894e19 100644 --- a/KeyVaultExplorer/Services/AuthService.cs +++ b/KeyVaultExplorer/Services/AuthService.cs @@ -25,6 +25,9 @@ public class AuthService public IAccount Account { get; private set; } + + // Property to get the current cloud's portal URL + public string PortalUrl => GetCurrentCloudEnvironment().PortalUrl; public AuthService() { @@ -33,13 +36,28 @@ public AuthService() var customClientId = (string?)settings.AppSettings.CustomClientId ?? Constants.ClientId; var settingsPageClientIdCheckbox = (bool?)settings.AppSettings.SettingsPageClientIdCheckbox ?? false; string clientId = settingsPageClientIdCheckbox && !string.IsNullOrEmpty(customClientId) ? customClientId : Constants.ClientId; + + // Get the cloud environment from settings + var cloudSetting = settings.AppSettings.AzureCloud ?? "Public"; + var azureCloud = Enum.TryParse(cloudSetting, out var parsed) ? parsed : AzureCloud.Public; + var cloudEnvironment = CloudEnvironment.GetCloudEnvironment(azureCloud); authenticationClient = PublicClientApplicationBuilder.Create(clientId) + .WithAuthority(cloudEnvironment.Authority) .WithRedirectUri($"msal{clientId}://auth") .WithRedirectUri("http://localhost") .WithIosKeychainSecurityGroup("us.sidesteplabs.keyvaultexplorer") .Build(); } + + // Helper method to get current cloud environment + private CloudEnvironment GetCurrentCloudEnvironment() + { + var settings = Defaults.Locator.GetRequiredService(); + var cloudSetting = settings.AppSettings.AzureCloud ?? "Public"; + var azureCloud = Enum.TryParse(cloudSetting, out var parsed) ? parsed : AzureCloud.Public; + return CloudEnvironment.GetCloudEnvironment(azureCloud); + } // Propagates notification that the operation should be cancelled. public async Task LoginAsync(CancellationToken cancellationToken) @@ -166,13 +184,15 @@ public async Task GetAzureArmTokenSilent() accounts = await authenticationClient.GetAccountsAsync(); Account = accounts.First(); } - return await authenticationClient.AcquireTokenSilent(Constants.AzureRMScope, accounts.First()).ExecuteAsync(); + var cloudEnvironment = GetCurrentCloudEnvironment(); + return await authenticationClient.AcquireTokenSilent(Constants.GetAzureResourceManagerScope(cloudEnvironment), accounts.First()).ExecuteAsync(); } public async Task GetAzureKeyVaultTokenSilent() { await AttachTokenCache(); var accounts = await authenticationClient.GetAccountsAsync(); - return await authenticationClient.AcquireTokenSilent(Constants.KvScope, accounts.First()).ExecuteAsync(); + var cloudEnvironment = GetCurrentCloudEnvironment(); + return await authenticationClient.AcquireTokenSilent(Constants.GetKeyVaultScope(cloudEnvironment), accounts.First()).ExecuteAsync(); } } \ No newline at end of file diff --git a/KeyVaultExplorer/ViewModels/PropertiesPageViewModel.cs b/KeyVaultExplorer/ViewModels/PropertiesPageViewModel.cs index 070c740..bb8c970 100644 --- a/KeyVaultExplorer/ViewModels/PropertiesPageViewModel.cs +++ b/KeyVaultExplorer/ViewModels/PropertiesPageViewModel.cs @@ -358,7 +358,7 @@ private async Task NewVersion() private void OpenInAzure() { if (OpenedItem is null) return; - var uri = $"https://portal.azure.com/#@{_authService.TenantName}/asset/Microsoft_Azure_KeyVault/{OpenedItem.Type}/{OpenedItem.Id}"; + var uri = $"{_authService.PortalUrl}/#@{_authService.TenantName}/asset/Microsoft_Azure_KeyVault/{OpenedItem.Type}/{OpenedItem.Id}"; Process.Start(new ProcessStartInfo(uri) { UseShellExecute = true, Verb = "open" }); } diff --git a/KeyVaultExplorer/ViewModels/SettingsPageViewModel.cs b/KeyVaultExplorer/ViewModels/SettingsPageViewModel.cs index 4b569aa..68da83d 100644 --- a/KeyVaultExplorer/ViewModels/SettingsPageViewModel.cs +++ b/KeyVaultExplorer/ViewModels/SettingsPageViewModel.cs @@ -50,6 +50,12 @@ public partial class SettingsPageViewModel : ViewModelBase [ObservableProperty] private string customClientId; + + [ObservableProperty] + private string[] azureClouds = ["Public", "USGovernment"]; + + [ObservableProperty] + private string selectedAzureCloud; public SettingsPageViewModel() { @@ -66,6 +72,7 @@ public SettingsPageViewModel() CurrentAppTheme = jsonSettings.AppTheme ?? "System"; CustomClientId = jsonSettings.CustomClientId; SettingsPageClientIdCheckbox = jsonSettings.SettingsPageClientIdCheckbox; + SelectedAzureCloud = jsonSettings.AzureCloud ?? "Public"; //NavigationLayoutMode = s.NavigationLayoutMode; }, DispatcherPriority.MaxValue); } @@ -145,6 +152,16 @@ partial void OnClearClipboardTimeoutChanging(int oldValue, int newValue) }, DispatcherPriority.Background); } + partial void OnSelectedAzureCloudChanged(string value) + { + Dispatcher.UIThread.InvokeAsync(async () => + { + await Task.Delay(50); + await AddOrUpdateAppSettings(nameof(AppSettings.AzureCloud), SelectedAzureCloud); + }, + DispatcherPriority.Background); + } + [RelayCommand] private async Task SetSplitViewDisplayMode(string splitViewDisplayMode) { diff --git a/KeyVaultExplorer/Views/Pages/SettingsPage.axaml b/KeyVaultExplorer/Views/Pages/SettingsPage.axaml index 4100ab5..06cde1f 100644 --- a/KeyVaultExplorer/Views/Pages/SettingsPage.axaml +++ b/KeyVaultExplorer/Views/Pages/SettingsPage.axaml @@ -140,6 +140,27 @@ + + + + + + + + + + +