1
0
mirror of https://github.com/bitwarden/server synced 2026-02-24 16:42:52 +00:00

fix(redirect): [PM-30810] Https Redirection for Cloud Users - Clarified messaging around how specific clients will build the response as well as added tests.

This commit is contained in:
Patrick Pimentel
2026-01-23 17:57:39 -05:00
parent dd6c49e214
commit 094754b58f
3 changed files with 254 additions and 15 deletions

View File

@@ -0,0 +1,17 @@
namespace Bit.Core.Auth.Enums;
/// <summary>
/// Deeplink scheme values used for mobile client redirects after Duo authentication.
/// </summary>
public enum DeeplinkScheme : byte
{
/// <summary>
/// HTTPS scheme used for Bitwarden cloud-hosted environments.
/// </summary>
Https = 0,
/// <summary>
/// Custom bitwarden:// scheme used for self-hosted environments.
/// </summary>
Bitwarden = 1,
}

View File

@@ -2,6 +2,7 @@
#nullable disable
using System.Globalization;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Context;
@@ -76,6 +77,15 @@ public interface IDuoUniversalTokenService
/// <param name="provider">TwoFactorProvider Duo or OrganizationDuo</param>
/// <returns>Duo.Client object or null</returns>
Task<Duo.Client> BuildDuoTwoFactorClientAsync(TwoFactorProvider provider);
/// <summary>
/// Builds the redirect URI for Duo authentication based on the client type and request context.
/// Mobile clients include a deeplinkScheme parameter (https for cloud, bitwarden for self-hosted).
/// Desktop clients always use the bitwarden scheme.
/// Other clients (web, browser, cli) do not include the deeplinkScheme parameter.
/// </summary>
/// <returns>The redirect URI to be used for Duo authentication</returns>
string BuildDuoTwoFactorRedirectUri();
}
public class DuoUniversalTokenService(
@@ -187,7 +197,7 @@ public class DuoUniversalTokenService(
normalizedHost.EndsWith(".localhost");
}
private static string GetDeeplinkSchemeOverride(HttpContext httpContext)
private static DeeplinkScheme? GetDeeplinkSchemeOverride(HttpContext httpContext)
{
if (httpContext == null)
{
@@ -204,13 +214,13 @@ public class DuoUniversalTokenService(
// 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().ToLowerInvariant();
var candidate = (overrideFromQuery ?? overrideFromHeader)?.Trim();
// Allow only the two supported values
return candidate is "https" or "bitwarden" ? candidate : null;
return Enum.TryParse<DeeplinkScheme>(candidate, ignoreCase: true, out var scheme) ? scheme : null;
}
public async Task<Duo.Client> BuildDuoTwoFactorClientAsync(TwoFactorProvider provider)
public string BuildDuoTwoFactorRedirectUri()
{
// Fetch Client name from header value since duo auth can be initiated from multiple clients and we want
// to redirect back to the initiating client
@@ -221,28 +231,38 @@ public class DuoUniversalTokenService(
: ClientType.Web;
var clientName = clientType.ToString().ToLowerInvariant();
string redirectUri;
// Handle mobile case separately because mobile needs to define the scheme ahead of time
// for security reasons.
if (clientType == ClientType.Mobile)
{
var requestHost = _currentContext.HttpContext.Request.Host.Host;
var deeplinkScheme = GetDeeplinkSchemeOverride(_currentContext.HttpContext) ??
(IsBitwardenCloudHost(requestHost) ? "https" : "bitwarden");
redirectUri = string.Format(CultureInfo.InvariantCulture,
(IsBitwardenCloudHost(requestHost) ? DeeplinkScheme.Https : DeeplinkScheme.Bitwarden);
return string.Format(CultureInfo.InvariantCulture,
"{0}/duo-redirect-connector.html?client={1}&deeplinkScheme={2}",
_globalSettings.BaseServiceUri.Vault, clientName, deeplinkScheme);
_globalSettings.BaseServiceUri.Vault, clientName, deeplinkScheme.ToString().ToLowerInvariant());
}
// All other clients will not use the deep link scheme property built into the duo token provided
// back from their api.
else
// Explicitly have the desktop client use the bitwarden scheme. See the complimentary
// duo web connector in the client project.
if (clientType == ClientType.Desktop)
{
redirectUri = string.Format(CultureInfo.InvariantCulture,
"{0}/duo-redirect-connector.html?client={1}",
_globalSettings.BaseServiceUri.Vault, clientName);
return string.Format(CultureInfo.InvariantCulture,
"{0}/duo-redirect-connector.html?client={1}&deeplinkScheme={2}",
_globalSettings.BaseServiceUri.Vault, clientName, DeeplinkScheme.Bitwarden.ToString().ToLowerInvariant());
}
// All other clients will not provide an explicit handling. See the complimentary
// duo web connector in the client project to understand how defaulting is handled.
return string.Format(CultureInfo.InvariantCulture,
"{0}/duo-redirect-connector.html?client={1}",
_globalSettings.BaseServiceUri.Vault, clientName);
}
public async Task<Duo.Client> BuildDuoTwoFactorClientAsync(TwoFactorProvider provider)
{
var redirectUri = BuildDuoTwoFactorRedirectUri();
var client = new Duo.ClientBuilder(
(string)provider.MetaData["ClientId"],
(string)provider.MetaData["ClientSecret"],