1
0
mirror of https://github.com/bitwarden/server synced 2026-02-08 12:40:08 +00:00

fix(redirect): [PM-30810] Https Redirection for Cloud Users - Looking at payload body and removed header override solution.

This commit is contained in:
Patrick Pimentel
2026-02-06 10:53:52 -05:00
parent d128c5d523
commit bd36e9ca40
2 changed files with 108 additions and 12 deletions

View File

@@ -181,10 +181,13 @@ public class DuoUniversalTokenService(
return null;
}
// Querystring has precedence over header for manual local testing
var overrideFromQuery = httpContext.Request.Query["deeplinkScheme"].FirstOrDefault();
var overrideFromHeader = httpContext.Request.Headers["Bitwarden-Deeplink-Scheme"].FirstOrDefault();
var candidate = (overrideFromQuery ?? overrideFromHeader)?.Trim();
// Form data (POST body) has precedence, then header as fallback
string overrideFromForm = null;
if (httpContext.Request.HasFormContentType)
{
overrideFromForm = httpContext.Request.Form["deeplinkScheme"].FirstOrDefault();
}
var candidate = overrideFromForm?.Trim();
// Allow only the two supported values
return Enum.TryParse<DuoDeeplinkScheme>(candidate, ignoreCase: true, out var scheme) ? scheme : null;

View File

@@ -103,16 +103,79 @@ public class DuoUniversalTokenServiceTests
Assert.Equal(result, expectedResponse);
}
[Theory]
[BitAutoData("vault.bitwarden.com")] // Cloud US
[BitAutoData("vault.bitwarden.eu")] // Cloud EU
public void BuildDuoTwoFactorRedirectUri_MobileClient_CloudHost_ReturnsHttpsScheme(
string requestHost)
[Fact]
public void BuildDuoTwoFactorRedirectUri_MobileClient_NoOverride_DefaultsToBitwardenScheme()
{
// Arrange
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["Bitwarden-Client-Name"] = "mobile";
httpContext.Request.Host = new HostString(requestHost);
httpContext.Request.Host = new HostString("vault.bitwarden.com");
var currentContext = Substitute.For<ICurrentContext>();
currentContext.HttpContext.Returns(httpContext);
var globalSettings = new CoreSettings.GlobalSettings
{
BaseServiceUri = new CoreSettings.GlobalSettings.BaseServiceUriSettings(new CoreSettings.GlobalSettings()) { Vault = "https://vault.bitwarden.com" }
};
var sut = new DuoUniversalTokenService(currentContext, globalSettings);
// Act
var result = InvokeBuildDuoTwoFactorRedirectUri(sut);
// Assert
Assert.Contains("client=mobile", result);
Assert.Contains("deeplinkScheme=bitwarden", result);
}
[Theory]
[BitAutoData("https")]
[BitAutoData("bitwarden")]
public void BuildDuoTwoFactorRedirectUri_MobileClient_WithFormOverride_UsesOverrideScheme(
string schemeOverride)
{
// Arrange
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["Bitwarden-Client-Name"] = "mobile";
httpContext.Request.ContentType = "application/x-www-form-urlencoded";
httpContext.Request.Form = new FormCollection(new Dictionary<string, Microsoft.Extensions.Primitives.StringValues>
{
{ "deeplinkScheme", schemeOverride }
});
httpContext.Request.Host = new HostString("vault.bitwarden.com");
var currentContext = Substitute.For<ICurrentContext>();
currentContext.HttpContext.Returns(httpContext);
var globalSettings = new CoreSettings.GlobalSettings
{
BaseServiceUri = new CoreSettings.GlobalSettings.BaseServiceUriSettings(new CoreSettings.GlobalSettings()) { Vault = "https://vault.bitwarden.com" }
};
var sut = new DuoUniversalTokenService(currentContext, globalSettings);
// Act
var result = InvokeBuildDuoTwoFactorRedirectUri(sut);
// Assert
Assert.Contains("client=mobile", result);
Assert.Contains($"deeplinkScheme={schemeOverride.ToLowerInvariant()}", result);
}
[Fact]
public void BuildDuoTwoFactorRedirectUri_MobileClient_FormOverrideTakesPrecedenceOverHeader()
{
// Arrange
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["Bitwarden-Client-Name"] = "mobile";
httpContext.Request.Headers["Bitwarden-Deeplink-Scheme"] = "bitwarden";
httpContext.Request.ContentType = "application/x-www-form-urlencoded";
httpContext.Request.Form = new FormCollection(new Dictionary<string, Microsoft.Extensions.Primitives.StringValues>
{
{ "deeplinkScheme", "https" }
});
httpContext.Request.Host = new HostString("vault.bitwarden.com");
var currentContext = Substitute.For<ICurrentContext>();
currentContext.HttpContext.Returns(httpContext);
@@ -130,7 +193,37 @@ public class DuoUniversalTokenServiceTests
// Assert
Assert.Contains("client=mobile", result);
Assert.Contains("deeplinkScheme=https", result);
Assert.StartsWith("https://vault.bitwarden.com/duo-redirect-connector.html", result);
}
[Theory]
[BitAutoData("invalid")]
[BitAutoData("unknown")]
[BitAutoData("")]
public void BuildDuoTwoFactorRedirectUri_MobileClient_InvalidOverride_DefaultsToBitwardenScheme(
string invalidScheme)
{
// Arrange
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers["Bitwarden-Client-Name"] = "mobile";
httpContext.Request.Headers["Bitwarden-Deeplink-Scheme"] = invalidScheme;
httpContext.Request.Host = new HostString("vault.bitwarden.com");
var currentContext = Substitute.For<ICurrentContext>();
currentContext.HttpContext.Returns(httpContext);
var globalSettings = new CoreSettings.GlobalSettings
{
BaseServiceUri = new CoreSettings.GlobalSettings.BaseServiceUriSettings(new CoreSettings.GlobalSettings()) { Vault = "https://vault.bitwarden.com" }
};
var sut = new DuoUniversalTokenService(currentContext, globalSettings);
// Act
var result = InvokeBuildDuoTwoFactorRedirectUri(sut);
// Assert
Assert.Contains("client=mobile", result);
Assert.Contains("deeplinkScheme=bitwarden", result);
}
[Theory]
@@ -300,6 +393,6 @@ public class DuoUniversalTokenServiceTests
// Assert
Assert.Contains("client=mobile", result);
Assert.Contains("deeplinkScheme=https", result);
Assert.Contains("deeplinkScheme=bitwarden", result);
}
}