Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions src/UniGetUI.Avalonia/UniGetUI.Avalonia.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.*" />
<PackageReference Include="Octokit" Version="14.0.0" />
<PackageReference Include="Avalonia.Controls.WebView" Version="12.0.0-rc1" />
<PackageReference Include="Tmds.DBus.Protocol" Version="0.92.0" />
</ItemGroup>

<ItemGroup>
Expand Down
7 changes: 7 additions & 0 deletions src/UniGetUI.Core.SecureSettings/SecureSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ namespace UniGetUI.Core.SettingsEngine.SecureSettings;

public static class SecureSettings
{
public static string? TEST_SecureSettingsRootOverride { private get; set; }

// Various predefined secure settings keys
public enum K
{
Expand Down Expand Up @@ -140,6 +142,11 @@ public static int ApplyForUser(string username, string setting, bool enable)

private static string GetSecureSettingsRoot()
{
if (TEST_SecureSettingsRootOverride is not null)
{
return TEST_SecureSettingsRootOverride;
}

if (OperatingSystem.IsWindows())
{
return Path.Join(
Expand Down
111 changes: 111 additions & 0 deletions src/UniGetUI.Core.Settings.Tests/SecureSettingsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using System.Reflection;
using UniGetUI.Core.Tools;
using SecureSettingsStore = UniGetUI.Core.SettingsEngine.SecureSettings.SecureSettings;

namespace UniGetUI.Core.SettingsEngine.Tests;

public sealed class SecureSettingsTests : IDisposable
{
private readonly string _testRoot;

public SecureSettingsTests()
{
_testRoot = Path.Combine(Path.GetTempPath(), $"UniGetUI-SecureSettingsTests-{Guid.NewGuid():N}");
Directory.CreateDirectory(_testRoot);
SecureSettingsStore.TEST_SecureSettingsRootOverride = _testRoot;
ClearSecureSettingsCache();
}

public void Dispose()
{
ClearSecureSettingsCache();
SecureSettingsStore.TEST_SecureSettingsRootOverride = null;

if (Directory.Exists(_testRoot))
{
Directory.Delete(_testRoot, true);
}
}

[Theory]
[InlineData(SecureSettingsStore.K.AllowCLIArguments, "AllowCLIArguments")]
[InlineData(SecureSettingsStore.K.AllowImportingCLIArguments, "AllowImportingCLIArguments")]
[InlineData(SecureSettingsStore.K.AllowPrePostOpCommand, "AllowPrePostInstallCommands")]
[InlineData(SecureSettingsStore.K.AllowImportPrePostOpCommands, "AllowImportingPrePostInstallCommands")]
[InlineData(SecureSettingsStore.K.ForceUserGSudo, "ForceUserGSudo")]
[InlineData(SecureSettingsStore.K.AllowCustomManagerPaths, "AllowCustomManagerPaths")]
public void ResolveKey_ReturnsExpectedMappings(SecureSettingsStore.K key, string expected)
{
Assert.Equal(expected, SecureSettingsStore.ResolveKey(key));
}

[Fact]
public void ResolveKey_ThrowsForUnsetAndUnknownKeys()
{
Assert.Throws<InvalidDataException>(() =>
SecureSettingsStore.ResolveKey(SecureSettingsStore.K.Unset)
);
Assert.Throws<KeyNotFoundException>(() =>
SecureSettingsStore.ResolveKey((SecureSettingsStore.K)999)
);
}

[Fact]
public void Get_ReturnsFalseWhenSettingDoesNotExist()
{
Assert.False(SecureSettingsStore.Get(SecureSettingsStore.K.AllowCLIArguments));
Assert.False(Directory.Exists(GetCurrentUserSettingsDirectory()));
}

[Fact]
public void ApplyForUser_CreatesAndRemovesSanitizedFile()
{
const string username = "test:user?";
const string setting = "setting<with>invalid|chars";

Assert.Equal(0, SecureSettingsStore.ApplyForUser(username, setting, true));
Assert.True(File.Exists(GetSettingsFilePath(username, setting)));

Assert.Equal(0, SecureSettingsStore.ApplyForUser(username, setting, false));
Assert.False(File.Exists(GetSettingsFilePath(username, setting)));
}

[Fact]
public void Get_RefreshesCachedValueAfterApplyForUserWrites()
{
string username = Environment.UserName;
string setting = SecureSettingsStore.ResolveKey(SecureSettingsStore.K.AllowCLIArguments);

Assert.False(SecureSettingsStore.Get(SecureSettingsStore.K.AllowCLIArguments));

Assert.Equal(0, SecureSettingsStore.ApplyForUser(username, setting, true));
Assert.True(File.Exists(GetSettingsFilePath(username, setting)));
Assert.True(SecureSettingsStore.Get(SecureSettingsStore.K.AllowCLIArguments));

Assert.Equal(0, SecureSettingsStore.ApplyForUser(username, setting, false));
Assert.False(File.Exists(GetSettingsFilePath(username, setting)));
Assert.False(SecureSettingsStore.Get(SecureSettingsStore.K.AllowCLIArguments));
}

private string GetCurrentUserSettingsDirectory() =>
Path.Combine(_testRoot, CoreTools.MakeValidFileName(Environment.UserName));

private string GetSettingsFilePath(string username, string setting) =>
Path.Combine(
_testRoot,
CoreTools.MakeValidFileName(username),
CoreTools.MakeValidFileName(setting)
);

private static void ClearSecureSettingsCache()
{
FieldInfo? cacheField = typeof(SecureSettingsStore).GetField(
"_cache",
BindingFlags.NonPublic | BindingFlags.Static
);
Assert.NotNull(cacheField);

var cache = Assert.IsType<Dictionary<string, bool>>(cacheField.GetValue(null));
cache.Clear();
}
}
83 changes: 83 additions & 0 deletions src/UniGetUI.Core.Settings.Tests/SettingsImportExportTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System.Text.Json;
using UniGetUI.Core.Data;

namespace UniGetUI.Core.SettingsEngine.Tests;

public sealed class SettingsImportExportTests : IDisposable
{
private readonly string _testRoot = Path.Combine(
Path.GetTempPath(),
nameof(SettingsImportExportTests),
Guid.NewGuid().ToString("N")
);

public SettingsImportExportTests()
{
Directory.CreateDirectory(_testRoot);
CoreData.TEST_DataDirectoryOverride = Path.Combine(_testRoot, "Data");
Directory.CreateDirectory(CoreData.UniGetUIUserConfigurationDirectory);
Settings.ResetSettings();
}

public void Dispose()
{
Settings.ResetSettings();
CoreData.TEST_DataDirectoryOverride = null;
if (Directory.Exists(_testRoot))
{
Directory.Delete(_testRoot, recursive: true);
}
}

[Fact]
public void ExportToStringJson_ExcludesSensitiveFiles()
{
Settings.Set(Settings.K.FreshBoolSetting, true);
Settings.SetValue(Settings.K.FreshValue, "configured");
File.WriteAllText(Path.Combine(CoreData.UniGetUIUserConfigurationDirectory, "TelemetryClientToken"), "secret");
File.WriteAllText(Path.Combine(CoreData.UniGetUIUserConfigurationDirectory, "CurrentSessionToken"), "secret");

var exported = JsonSerializer.Deserialize<Dictionary<string, string>>(Settings.ExportToString_JSON());

Assert.NotNull(exported);
Assert.Contains(Settings.ResolveKey(Settings.K.FreshBoolSetting), exported.Keys);
Assert.Equal("configured", exported[Settings.ResolveKey(Settings.K.FreshValue)]);
Assert.DoesNotContain("TelemetryClientToken", exported.Keys);
Assert.DoesNotContain("CurrentSessionToken", exported.Keys);
}

[Fact]
public void ImportFromStringJson_ResetsExistingFilesAndReloadsCache()
{
Settings.Set(Settings.K.Test1, true);
Settings.SetValue(Settings.K.FreshValue, "old-value");

string importedJson = JsonSerializer.Serialize(
new Dictionary<string, string>
{
[Settings.ResolveKey(Settings.K.Test2)] = "",
[Settings.ResolveKey(Settings.K.FreshValue)] = "new-value",
}
);

Settings.ImportFromString_JSON(importedJson);

Assert.False(File.Exists(Path.Combine(CoreData.UniGetUIUserConfigurationDirectory, Settings.ResolveKey(Settings.K.Test1))));
Assert.True(Settings.Get(Settings.K.Test2));
Assert.Equal("new-value", Settings.GetValue(Settings.K.FreshValue));
}

[Fact]
public void ImportFromFileJson_CopiesSourceWhenBackupLivesInSettingsDirectory()
{
Settings.SetValue(Settings.K.FreshValue, "before-import");
string exportPath = Path.Combine(CoreData.UniGetUIUserConfigurationDirectory, "settings-backup.json");

Settings.ExportToFile_JSON(exportPath);
Settings.SetValue(Settings.K.FreshValue, "after-export");

Settings.ImportFromFile_JSON(exportPath);

Assert.Equal("before-import", Settings.GetValue(Settings.K.FreshValue));
}
}
3 changes: 3 additions & 0 deletions src/UniGetUI.Core.Settings.Tests/TestAssembly.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using Xunit;

[assembly: CollectionBehavior(DisableTestParallelization = true)]
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

<ItemGroup>
<ProjectReference Include="..\UniGetUI.Core.Settings\UniGetUI.Core.Settings.csproj" />
<ProjectReference Include="..\UniGetUI.Core.SecureSettings\UniGetUI.Core.SecureSettings.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
5 changes: 5 additions & 0 deletions src/UniGetUI.Core.Settings/SettingsEngine_ImportExport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ public static void ImportFromString_JSON(string jsonContent)

public static void ResetSettings()
{
booleanSettings.Clear();
valueSettings.Clear();
listSettings.Clear();
_dictionarySettings.Clear();

foreach (
string entry in Directory.EnumerateFiles(CoreData.UniGetUIUserConfigurationDirectory)
)
Expand Down
Loading
Loading