Add explicit client version disable mechanism to API (#1548)
This commit is contained in:
@@ -110,10 +110,11 @@ public class AuthController(IAliasServerDbContextFactory dbContextFactory, UserM
|
|||||||
|
|
||||||
if (AppInfo.MinimumClientVersions.TryGetValue(platform, out var minimumVersion))
|
if (AppInfo.MinimumClientVersions.TryGetValue(platform, out var minimumVersion))
|
||||||
{
|
{
|
||||||
if (VersionHelper.IsVersionEqualOrNewer(clientVersion, minimumVersion))
|
// Check if version meets minimum requirement AND is not in blocked list
|
||||||
{
|
var meetsMinimum = VersionHelper.IsVersionEqualOrNewer(clientVersion, minimumVersion);
|
||||||
clientSupported = true;
|
var isBlocked = VersionHelper.IsVersionBlocked(platform, clientVersion, AppInfo.UnsupportedClientVersions);
|
||||||
}
|
|
||||||
|
clientSupported = meetsMinimum && !isBlocked;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -59,4 +59,36 @@ public static class VersionHelper
|
|||||||
// Compare the versions
|
// Compare the versions
|
||||||
return v1 >= v2;
|
return v1 >= v2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a version is blocked for a specific platform.
|
||||||
|
/// Checks both platform-specific blocks and global blocks (using "*" key).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="platform">The platform to check (e.g., "chrome", "ios").</param>
|
||||||
|
/// <param name="version">The version to check.</param>
|
||||||
|
/// <param name="blockedVersions">Dictionary of platform to blocked versions. Use "*" for global blocks.</param>
|
||||||
|
/// <returns>True if the version is blocked for this platform, false otherwise.</returns>
|
||||||
|
public static bool IsVersionBlocked(string platform, string version, IReadOnlyDictionary<string, HashSet<string>> blockedVersions)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(version) || blockedVersions == null || blockedVersions.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check global blocks (applies to all platforms)
|
||||||
|
if (blockedVersions.TryGetValue("*", out var globalBlocked) && globalBlocked.Contains(version))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check platform-specific blocks
|
||||||
|
if (!string.IsNullOrEmpty(platform) &&
|
||||||
|
blockedVersions.TryGetValue(platform, out var platformBlocked) &&
|
||||||
|
platformBlocked.Contains(version))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public static class AppInfo
|
|||||||
/// for all clients as we are using a monorepo to build all clients from the same source code. But it's
|
/// for all clients as we are using a monorepo to build all clients from the same source code. But it's
|
||||||
/// possible to override the minimum client version for a specific client if needed.
|
/// possible to override the minimum client version for a specific client if needed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string MinimumClientVersion = "0.26.1";
|
public const string MinimumClientVersion = "0.12.0";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a dictionary of minimum supported client versions that the WebApi supports.
|
/// Gets a dictionary of minimum supported client versions that the WebApi supports.
|
||||||
@@ -68,6 +68,24 @@ public static class AppInfo
|
|||||||
{ "android", MinimumClientVersion },
|
{ "android", MinimumClientVersion },
|
||||||
}.AsReadOnly();
|
}.AsReadOnly();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a dictionary of specific client versions that are explicitly unsupported (blocked) per platform.
|
||||||
|
/// This is useful for blocking specific versions with (critical) bugs while still allowing
|
||||||
|
/// older versions that predate the bug to continue working.
|
||||||
|
/// For example: if 0.26.0 has a critical bug fixed in 0.26.1, we can block only 0.26.0
|
||||||
|
/// without affecting users on 0.25.x who may still have compatible vaults.
|
||||||
|
/// Use "*" as the platform key to block a version across all platforms.
|
||||||
|
/// </summary>
|
||||||
|
public static IReadOnlyDictionary<string, HashSet<string>> UnsupportedClientVersions { get; } = new Dictionary<string, HashSet<string>>
|
||||||
|
{
|
||||||
|
// Block version across all platforms: "*" applies to all clients.
|
||||||
|
{ "*", ["0.26.0"] }, // Version with vault migration bug, fixed in 0.26.1
|
||||||
|
|
||||||
|
// Platform-specific blocks (examples):
|
||||||
|
// { "chrome", ["0.25.0"] },
|
||||||
|
// { "ios", ["0.24.0", "0.24.1"] },
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the build number, typically used in CI/CD pipelines.
|
/// Gets the build number, typically used in CI/CD pipelines.
|
||||||
/// Can be overridden at build time.
|
/// Can be overridden at build time.
|
||||||
|
|||||||
@@ -68,4 +68,96 @@ public class VersionTests
|
|||||||
var version2 = "1.1.0";
|
var version2 = "1.1.0";
|
||||||
Assert.That(VersionHelper.IsVersionEqualOrNewer(version1, version2), Is.True);
|
Assert.That(VersionHelper.IsVersionEqualOrNewer(version1, version2), Is.True);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test that a version in the global blocked list is correctly identified as blocked.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void VersionBlockedReturnsTrueForGloballyBlockedVersion()
|
||||||
|
{
|
||||||
|
var blockedVersions = new Dictionary<string, HashSet<string>>
|
||||||
|
{
|
||||||
|
{ "*", ["0.26.0", "0.27.0"] },
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("chrome", "0.26.0", blockedVersions), Is.True);
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("ios", "0.27.0", blockedVersions), Is.True);
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("android", "0.26.0", blockedVersions), Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test that a version in the platform-specific blocked list is correctly identified as blocked.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void VersionBlockedReturnsTrueForPlatformSpecificBlockedVersion()
|
||||||
|
{
|
||||||
|
var blockedVersions = new Dictionary<string, HashSet<string>>
|
||||||
|
{
|
||||||
|
{ "chrome", ["0.25.0"] },
|
||||||
|
{ "ios", ["0.24.0"] },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Platform-specific blocks should work
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("chrome", "0.25.0", blockedVersions), Is.True);
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("ios", "0.24.0", blockedVersions), Is.True);
|
||||||
|
|
||||||
|
// Other platforms should not be blocked
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("firefox", "0.25.0", blockedVersions), Is.False);
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("android", "0.24.0", blockedVersions), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test that global and platform-specific blocks work together.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void VersionBlockedCombinesGlobalAndPlatformSpecific()
|
||||||
|
{
|
||||||
|
var blockedVersions = new Dictionary<string, HashSet<string>>
|
||||||
|
{
|
||||||
|
{ "*", ["0.26.0"] },
|
||||||
|
{ "chrome", ["0.25.0"] },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Global block applies to all platforms for exact version
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("chrome", "0.26.0", blockedVersions), Is.True);
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("ios", "0.26.0", blockedVersions), Is.True);
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("ios", "0.26.1", blockedVersions), Is.False);
|
||||||
|
|
||||||
|
// Platform-specific block only applies to that platform
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("chrome", "0.25.0", blockedVersions), Is.True);
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("firefox", "0.25.0", blockedVersions), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test that a version not in the blocked list is correctly identified as not blocked.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void VersionBlockedReturnsFalseForNonBlockedVersion()
|
||||||
|
{
|
||||||
|
var blockedVersions = new Dictionary<string, HashSet<string>>
|
||||||
|
{
|
||||||
|
{ "*", ["0.26.0"] },
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("chrome", "0.25.3", blockedVersions), Is.False);
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("chrome", "0.26.1", blockedVersions), Is.False);
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("chrome", "0.27.0", blockedVersions), Is.False);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test that empty or null inputs are handled correctly.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void VersionBlockedHandlesEmptyInputs()
|
||||||
|
{
|
||||||
|
var blockedVersions = new Dictionary<string, HashSet<string>>
|
||||||
|
{
|
||||||
|
{ "*", ["0.26.0"] },
|
||||||
|
};
|
||||||
|
|
||||||
|
var emptyBlockedVersions = new Dictionary<string, HashSet<string>>();
|
||||||
|
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("chrome", string.Empty, blockedVersions), Is.False);
|
||||||
|
Assert.That(VersionHelper.IsVersionBlocked("chrome", "0.26.0", emptyBlockedVersions), Is.False);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user