Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/Infrastructure/AppInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void NotifyOwner()
catch (Exception ex) when (ex is IOException || ex is TimeoutException)
{
ExceptionHelper.WriteToEventLog(ex, EventLogEntryType.Warning);
TelemetryService.TrackFatalException(ex);
TelemetryService.Instance.TrackException(ex);
return;
}

Expand All @@ -90,7 +90,7 @@ public void NotifyOwner()
catch (Exception ex) when (ex is ObjectDisposedException || ex is InvalidOperationException || ex is IOException)
{
ExceptionHelper.WriteToEventLog(ex, EventLogEntryType.Warning);
TelemetryService.TrackFatalException(ex);
TelemetryService.Instance.TrackException(ex);
return;
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/Infrastructure/AppWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Sqlbi.Bravo.Infrastructure.Extensions;
using Sqlbi.Bravo.Infrastructure.Helpers;
using Sqlbi.Bravo.Infrastructure.Messages;
using Sqlbi.Bravo.Infrastructure.Policies;
using Sqlbi.Bravo.Infrastructure.Services;
using Sqlbi.Bravo.Infrastructure.Telemetry;
using Sqlbi.Bravo.Infrastructure.Windows.Interop;
Expand Down Expand Up @@ -37,12 +38,14 @@ internal partial class AppWindow : Form
private readonly IOptions<StartupSettings> _startupSettingsOptionsAccessor;
private readonly WebView2ProxyAuthHandler _proxyAuthHandler;
private readonly Color _startupThemeColor;
private readonly IPolicies _policies;

public AppWindow(IServiceProvider services, AppInstance instance)
{
_instance = instance;
_serverAddressProvider = services.GetRequiredService<IServerAddressProvider>();
_startupSettingsOptionsAccessor = services.GetRequiredService<IOptions<StartupSettings>>();
_policies = services.GetRequiredService<IPolicies>();
_proxyAuthHandler = new WebView2ProxyAuthHandler(WebProxyWrapper.Current);
_startupThemeColor = ThemeHelper.ShouldUseDarkMode(UserPreferences.Current.Theme) ? AppEnvironment.ThemeColorDark : AppEnvironment.ThemeColorLight;

Expand Down Expand Up @@ -307,7 +310,7 @@ private MemoryStream GetConfigJs()
token = AppEnvironment.ApiAuthenticationToken,
version = AppEnvironment.VersionInfo.Version,
options = BravoOptions.CreateFromUserPreferences(),
policies = BravoPolicies.Current,
policies = _policies,
culture = new
{
ietfLanguageTag = CultureInfo.CurrentCulture.IetfLanguageTag,
Expand All @@ -323,7 +326,7 @@ private MemoryStream GetConfigJs()
},
};

var script = $@"var CONFIG = { JsonSerializer.Serialize(config) };";
var script = $@"var CONFIG = { JsonSerializer.Serialize(config, options: new JsonSerializerOptions(JsonSerializerDefaults.Web)) };";

return new MemoryStream(Encoding.UTF8.GetBytes(script));
}
Expand Down

This file was deleted.

55 changes: 5 additions & 50 deletions src/Infrastructure/Configuration/Settings/UserSettings.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
namespace Sqlbi.Bravo.Infrastructure.Configuration.Settings
{
using Sqlbi.Bravo.Infrastructure.Security.Policies;
using Sqlbi.Bravo.Models;
using System.Text.Json;
using System.Text.Json.Serialization;

Expand Down Expand Up @@ -36,35 +34,17 @@ public class UserSettings : IUserSettings
public const bool DefaultUseSystemBrowserForAuthentication = false;
public const bool DefaultCustomTemplatesEnabled = true;

private bool _telemetryEnabled = DefaultTelemetryEnabled;
private UpdateChannelType _updateChannel = DefaultUpdateChannel;
private bool _updateCheckEnabled = DefaultUpdateCheckEnabled;
private bool _useSystemBrowserForAuthentication = DefaultUseSystemBrowserForAuthentication;
private bool _customTemplatesEnabled = DefaultCustomTemplatesEnabled;

[JsonPropertyName("telemetryEnabled")]
public bool TelemetryEnabled
{
get => _telemetryEnabled;
set => _telemetryEnabled = GetSetterValue(value, BravoPolicies.Current.TelemetryEnabledPolicy, BravoPolicies.Current.TelemetryEnabled);
}
public bool TelemetryEnabled { get; set; } = DefaultTelemetryEnabled;

[JsonPropertyName("diagnosticLevel")]
public DiagnosticLevelType DiagnosticLevel { get; set; } = DefaultDiagnosticLevel;

[JsonPropertyName("updateChannel")]
public UpdateChannelType UpdateChannel
{
get => _updateChannel;
set => _updateChannel = GetSetterValue(value, BravoPolicies.Current.UpdateChannelPolicy, BravoPolicies.Current.UpdateChannel);
}
public UpdateChannelType UpdateChannel { get; set; } = DefaultUpdateChannel;

[JsonPropertyName("updateCheckEnabled")]
public bool UpdateCheckEnabled
{
get => _updateCheckEnabled;
set => _updateCheckEnabled = GetSetterValue(value, BravoPolicies.Current.UpdateCheckEnabledPolicy, BravoPolicies.Current.UpdateCheckEnabled);
}
public bool UpdateCheckEnabled { get; set; } = DefaultUpdateCheckEnabled;

[JsonPropertyName("theme")]
public ThemeType Theme { get; set; } = DefaultTheme;
Expand All @@ -73,38 +53,13 @@ public bool UpdateCheckEnabled
public ProxySettings? Proxy { get; set; }

[JsonPropertyName("useSystemBrowserForAuthentication")]
public bool UseSystemBrowserForAuthentication
{
get => _useSystemBrowserForAuthentication;
set => _useSystemBrowserForAuthentication = GetSetterValue(value, BravoPolicies.Current.UseSystemBrowserForAuthenticationPolicy, BravoPolicies.Current.UseSystemBrowserForAuthentication);
}
public bool UseSystemBrowserForAuthentication { get; set; } = DefaultUseSystemBrowserForAuthentication;

[JsonPropertyName("customTemplatesEnabled")]
public bool CustomTemplatesEnabled
{
get => _customTemplatesEnabled;
set => _customTemplatesEnabled = GetSetterValue(value, BravoPolicies.Current.CustomTemplatesEnabledPolicy, BravoPolicies.Current.CustomTemplatesEnabled);
}
public bool CustomTemplatesEnabled { get; set; } = DefaultCustomTemplatesEnabled;

[JsonPropertyName("customOptions")]
public JsonElement? CustomOptions { get; set; }

//[JsonPropertyName("experimental")]
//public ExperimentalSettings? Experimental { get; set; }

private T GetSetterValue<T>(T setterValue, PolicyStatus policyStatus, T policyValue)
{
if (policyStatus == PolicyStatus.Forced)
{
return policyValue;
}
else if (policyStatus == PolicyStatus.NotConfigured)
{
return setterValue;
}

throw new BravoUnexpectedException($"Unexpected { nameof(PolicyStatus) } value ({ policyStatus })");
}
}

public enum ThemeType
Expand Down
6 changes: 0 additions & 6 deletions src/Infrastructure/Extensions/RegistryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,6 @@

internal static class RegistryExtensions
{
public static bool SubKeyExists(this RegistryKey registryKey, string subkeyName)
{
using var registrySubKey = registryKey.OpenSubKey(subkeyName);
return registrySubKey != null;
}

public static bool GetBoolValue(this RegistryKey registryKey, string subkeyName, string valueName)
{
var valueInt = GetIntValue(registryKey, subkeyName, valueName);
Expand Down
48 changes: 48 additions & 0 deletions src/Infrastructure/Policies/IPolicySource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
namespace Sqlbi.Bravo.Infrastructure.Policies
{
using Microsoft.Win32;

/// <summary>
/// Abstraction over a single raw policy value store (e.g. a registry key), so that
/// <see cref="Policies"/>' parsing/precedence logic does not depend on <see cref="RegistryKey"/>
/// directly and can be unit tested against a fake, without touching the real registry.
/// </summary>
internal interface IPolicySource
{
int? GetInt(string name);
string? GetString(string name);
}

/// <summary>
/// Typed reading conventions shared by every <see cref="IPolicySource"/>: a policy is a
/// DWORD (0/1 -> bool, or a defined enum member) or a string. Kept as extensions rather than
/// interface members so <see cref="IPolicySource"/> itself stays minimal (raw int/string only).
/// </summary>
internal static class PolicySourceExtensions
{
private const int PolicyDisabledValue = 0;
private const int PolicyEnabledValue = 1;

extension(IPolicySource source)
{
public bool? GetBool(string name)
{
return source.GetInt(name) switch
{
null => null, // Policy not set
PolicyDisabledValue => false,
PolicyEnabledValue => true,
_ => null, // Invalid policy value
};
}

public T? GetEnum<T>(string name) where T : struct, Enum
{
if (source.GetInt(name) is { } value && Enum.IsDefined(typeof(T), value))
return (T)(object)value;

return null; // Policy not set or invalid
}
}
}
}
29 changes: 29 additions & 0 deletions src/Infrastructure/Policies/Policies.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace Sqlbi.Bravo.Infrastructure.Policies
{
using Sqlbi.Bravo.Infrastructure.Configuration.Settings;

internal interface IPolicies
{
bool? TelemetryEnabled { get; }
UpdateChannelType? UpdateChannel { get; }
bool? UpdateCheckEnabled { get; }
bool? UseSystemBrowserForAuthentication { get; }
bool? BuiltInTemplatesEnabled { get; }
bool? CustomTemplatesEnabled { get; }
string? CustomTemplatesOrganizationRepositoryPath { get; }
}

/// <summary>
/// Immutable snapshot of Bravo's effective policy values. Pure data - see
/// <see cref="PoliciesFactory"/> for how instances are read from the registry, parsed,
/// and merged with LocalMachine/CurrentUser precedence.
/// </summary>
internal sealed record Policies(
bool? TelemetryEnabled,
UpdateChannelType? UpdateChannel,
bool? UpdateCheckEnabled,
bool? UseSystemBrowserForAuthentication,
bool? BuiltInTemplatesEnabled,
bool? CustomTemplatesEnabled,
string? CustomTemplatesOrganizationRepositoryPath) : IPolicies;
}
47 changes: 47 additions & 0 deletions src/Infrastructure/Policies/PoliciesFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace Sqlbi.Bravo.Infrastructure.Policies
{
using Microsoft.Win32;
using Sqlbi.Bravo.Infrastructure.Configuration.Settings;

/// <summary>
/// Builds <see cref="Policies"/> instances: parses a single <see cref="IPolicySource"/>,
/// and composes the effective policy set from LocalMachine + CurrentUser with precedence.
/// </summary>
internal static class PoliciesFactory
{
private const string OptionSettingsSubKeyName = @"SOFTWARE\Policies\SQLBI\Bravo\OptionSettings";

public static Policies Create()
{
using var machineKey = Registry.LocalMachine.OpenSubKey(OptionSettingsSubKeyName);
var machinePolicies = FromSource(new RegistryPolicySource(machineKey));

using var userKey = Registry.CurrentUser.OpenSubKey(OptionSettingsSubKeyName);
var userPolicies = FromSource(new RegistryPolicySource(userKey));

return Merge(machinePolicies, userPolicies);
}

internal static Policies FromSource(IPolicySource source) => new(
TelemetryEnabled: source.GetBool("TelemetryEnabled"),
UpdateChannel: source.GetEnum<UpdateChannelType>("UpdateChannel"),
UpdateCheckEnabled: source.GetBool("UpdateCheckEnabled"),
UseSystemBrowserForAuthentication: source.GetBool("UseSystemBrowserForAuthentication"),
BuiltInTemplatesEnabled: source.GetBool("BuiltInTemplatesEnabled"),
CustomTemplatesEnabled: source.GetBool("CustomTemplatesEnabled"),
CustomTemplatesOrganizationRepositoryPath: source.GetString("CustomTemplatesOrganizationRepositoryPath"));

internal static Policies Merge(Policies machinePolicies, Policies userPolicies)
{
// LocalMachine takes precedence over CurrentUser when both are configured
return new Policies(
TelemetryEnabled: machinePolicies.TelemetryEnabled ?? userPolicies.TelemetryEnabled,
UpdateChannel: machinePolicies.UpdateChannel ?? userPolicies.UpdateChannel,
UpdateCheckEnabled: machinePolicies.UpdateCheckEnabled ?? userPolicies.UpdateCheckEnabled,
UseSystemBrowserForAuthentication: machinePolicies.UseSystemBrowserForAuthentication ?? userPolicies.UseSystemBrowserForAuthentication,
BuiltInTemplatesEnabled: machinePolicies.BuiltInTemplatesEnabled ?? userPolicies.BuiltInTemplatesEnabled,
CustomTemplatesEnabled: machinePolicies.CustomTemplatesEnabled ?? userPolicies.CustomTemplatesEnabled,
CustomTemplatesOrganizationRepositoryPath: machinePolicies.CustomTemplatesOrganizationRepositoryPath ?? userPolicies.CustomTemplatesOrganizationRepositoryPath);
}
}
}
19 changes: 19 additions & 0 deletions src/Infrastructure/Policies/RegistryPolicySource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Sqlbi.Bravo.Infrastructure.Policies
{
using Microsoft.Win32;

/// <summary>
/// Adapter that bridges <see cref="IPolicySource"/> to a real <see cref="RegistryKey"/>.
/// Intentionally a thin pass-through with no logic of its own.
/// </summary>
internal sealed class RegistryPolicySource(RegistryKey? key) : IPolicySource
{
private readonly RegistryKey? _key = key;

public int? GetInt(string name)
=> _key?.GetValue(name) is int value ? value : null;

public string? GetString(string name)
=> _key?.GetValue(name) as string;
}
}
14 changes: 14 additions & 0 deletions src/Infrastructure/Policies/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace Sqlbi.Bravo.Infrastructure.Policies
{
using Microsoft.Extensions.DependencyInjection;

internal static class ServiceCollectionExtensions
{
public static IServiceCollection AddGroupPolicies(this IServiceCollection services)
{
services.AddSingleton<IPolicies>(_ => PoliciesFactory.Create());

return services;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Sqlbi.Bravo.Infrastructure.PowerBI.Cloud.Authentication
using Sqlbi.Bravo.Infrastructure.Configuration;
using Sqlbi.Bravo.Infrastructure.Extensions;
using Sqlbi.Bravo.Infrastructure.Helpers;
using Sqlbi.Bravo.Infrastructure.Policies;
using Sqlbi.Bravo.Infrastructure.PowerBI.Cloud;
using Msal = Microsoft.Identity.Client;

Expand All @@ -18,11 +19,13 @@ public interface ICloudAuthenticationClient
/// <summary>
/// Handles authentication with Microsoft Entra ID (Azure AD) using MSAL.NET, including token acquisition and cache management.
/// </summary>
internal sealed class CloudAuthenticationClient : ICloudAuthenticationClient
internal sealed class CloudAuthenticationClient(IPolicies policies) : ICloudAuthenticationClient
{
private const string SystemBrowserRedirectUri = "http://localhost";
private const string OrganizationalAccountsOnlyQueryParameter = "msafed=0"; // no Microsoft accounts (MSA) allowed

private readonly IPolicies _policies = policies;

public async Task<AuthenticationResult> AcquireTokenAsync(
CloudEnvironment environment, string email, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -68,10 +71,11 @@ public async Task ClearTokenCacheAsync(CloudEnvironment environment)
return await builder.ExecuteAsync(cancellationToken).ConfigureAwait(false);
}

private static async Task<Msal.AuthenticationResult> AcquireTokenInteractiveAsync(
private async Task<Msal.AuthenticationResult> AcquireTokenInteractiveAsync(
IPublicClientApplication client, string[] scopes, string email, string claims, CancellationToken cancellationToken)
{
var useEmbeddedBrowser = !UserPreferences.Current.UseSystemBrowserForAuthentication;
var useSystemBrowser = _policies.UseSystemBrowserForAuthentication ?? UserPreferences.Current.UseSystemBrowserForAuthentication;
var useEmbeddedBrowser = !useSystemBrowser;
var extraQueryParameters = OrganizationalAccountsOnlyQueryParameter;
var prompt = Prompt.SelectAccount;
var loginHint = email;
Expand Down Expand Up @@ -116,9 +120,10 @@ public async Task ClearTokenCacheAsync(CloudEnvironment environment)
}
}

private static IPublicClientApplication CreatePublicClient(CloudEnvironment environment)
private IPublicClientApplication CreatePublicClient(CloudEnvironment environment)
{
var useEmbeddedBrowser = !UserPreferences.Current.UseSystemBrowserForAuthentication;
var useSystemBrowser = _policies.UseSystemBrowserForAuthentication ?? UserPreferences.Current.UseSystemBrowserForAuthentication;
var useEmbeddedBrowser = !useSystemBrowser;
var redirectUri = (useEmbeddedBrowser ? environment.RedirectUri : SystemBrowserRedirectUri);

var builder = PublicClientApplicationBuilder.Create(environment.ClientId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal static class ServiceCollectionExtensions
{
internal const string PowerBIApiHttpClientName = "PowerBIApi";

public static IServiceCollection AddPowerBIServices(this IServiceCollection services)
public static IServiceCollection AddPowerBI(this IServiceCollection services)
{
services.AddHttpClient(PowerBIApiHttpClientName, (client) =>
{
Expand Down
Loading
Loading