mirror of
https://github.com/bitwarden/server
synced 2026-02-08 20:50:13 +00:00
399 lines
14 KiB
C#
399 lines
14 KiB
C#
using System.Reflection;
|
|
using Bit.Core.Auth.Identity.TokenProviders;
|
|
using Bit.Core.Auth.Models;
|
|
using Bit.Core.Context;
|
|
using Bit.Test.Common.AutoFixture;
|
|
using Bit.Test.Common.AutoFixture.Attributes;
|
|
using Microsoft.AspNetCore.Http;
|
|
using NSubstitute;
|
|
using Xunit;
|
|
using CoreSettings = Bit.Core.Settings;
|
|
|
|
namespace Bit.Core.Test.Auth.Services;
|
|
|
|
[SutProviderCustomize]
|
|
public class DuoUniversalTokenServiceTests
|
|
{
|
|
/// <summary>
|
|
/// Helper method to invoke the private BuildDuoTwoFactorRedirectUri method via reflection.
|
|
/// </summary>
|
|
private static string InvokeBuildDuoTwoFactorRedirectUri(DuoUniversalTokenService sut)
|
|
{
|
|
var method = typeof(DuoUniversalTokenService).GetMethod(
|
|
"BuildDuoTwoFactorRedirectUri",
|
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
|
return (string)method!.Invoke(sut, null)!;
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData("", "ClientId", "ClientSecret")]
|
|
[BitAutoData("api-valid.duosecurity.com", "", "ClientSecret")]
|
|
[BitAutoData("api-valid.duosecurity.com", "ClientId", "")]
|
|
public async void ValidateDuoConfiguration_InvalidConfig_ReturnsFalse(
|
|
string host, string clientId, string clientSecret, SutProvider<DuoUniversalTokenService> sutProvider)
|
|
{
|
|
// Arrange
|
|
/* AutoData handles arrangement */
|
|
|
|
// Act
|
|
var result = await sutProvider.Sut.ValidateDuoConfiguration(clientSecret, clientId, host);
|
|
|
|
// Assert
|
|
Assert.False(result);
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData(true, "api-valid.duosecurity.com")]
|
|
[BitAutoData(false, "invalid")]
|
|
[BitAutoData(false, "api-valid.duosecurity.com", null, "clientSecret")]
|
|
[BitAutoData(false, "api-valid.duosecurity.com", "ClientId", null)]
|
|
[BitAutoData(false, "api-valid.duosecurity.com", null, null)]
|
|
public void HasProperDuoMetadata_ReturnMatchesExpected(
|
|
bool expectedResponse, string host, string clientId,
|
|
string clientSecret, SutProvider<DuoUniversalTokenService> sutProvider)
|
|
{
|
|
// Arrange
|
|
var metaData = new Dictionary<string, object> { ["Host"] = host };
|
|
|
|
if (clientId != null)
|
|
{
|
|
metaData.Add("ClientId", clientId);
|
|
}
|
|
|
|
if (clientSecret != null)
|
|
{
|
|
metaData.Add("ClientSecret", clientSecret);
|
|
}
|
|
|
|
var provider = new TwoFactorProvider
|
|
{
|
|
MetaData = metaData
|
|
};
|
|
|
|
// Act
|
|
var result = sutProvider.Sut.HasProperDuoMetadata(provider);
|
|
|
|
// Assert
|
|
Assert.Equal(result, expectedResponse);
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData]
|
|
public void HasProperDuoMetadata_ProviderIsNull_ReturnsFalse(
|
|
SutProvider<DuoUniversalTokenService> sutProvider)
|
|
{
|
|
// Act
|
|
var result = sutProvider.Sut.HasProperDuoMetadata(null);
|
|
|
|
// Assert
|
|
Assert.False(result);
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData("api-valid.duosecurity.com", true)]
|
|
[BitAutoData("api-valid.duofederal.com", true)]
|
|
[BitAutoData("invalid", false)]
|
|
public void ValidDuoHost_HostIsValid_ReturnTrue(
|
|
string host, bool expectedResponse)
|
|
{
|
|
// Act
|
|
var result = DuoUniversalTokenService.ValidDuoHost(host);
|
|
|
|
// Assert
|
|
Assert.Equal(result, expectedResponse);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildDuoTwoFactorRedirectUri_MobileClient_NoOverride_DefaultsToBitwardenScheme()
|
|
{
|
|
// Arrange
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Headers["Bitwarden-Client-Name"] = "mobile";
|
|
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);
|
|
|
|
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=https", 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]
|
|
[BitAutoData("selfhosted.example.com")]
|
|
[BitAutoData("192.168.1.100")]
|
|
public void BuildDuoTwoFactorRedirectUri_MobileClient_SelfHosted_ReturnsBitwardenScheme(
|
|
string requestHost)
|
|
{
|
|
// Arrange
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Headers["Bitwarden-Client-Name"] = "mobile";
|
|
httpContext.Request.Host = new HostString(requestHost);
|
|
|
|
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.example.com" }
|
|
};
|
|
|
|
var sut = new DuoUniversalTokenService(currentContext, globalSettings);
|
|
|
|
// Act
|
|
var result = InvokeBuildDuoTwoFactorRedirectUri(sut);
|
|
|
|
// Assert
|
|
Assert.Contains("client=mobile", result);
|
|
Assert.Contains("deeplinkScheme=bitwarden", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildDuoTwoFactorRedirectUri_DesktopClient_ReturnsBitwardenScheme()
|
|
{
|
|
// Arrange
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Headers["Bitwarden-Client-Name"] = "desktop";
|
|
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=desktop", result);
|
|
Assert.Contains("deeplinkScheme=bitwarden", result);
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData("web")]
|
|
[BitAutoData("browser")]
|
|
[BitAutoData("cli")]
|
|
public void BuildDuoTwoFactorRedirectUri_NonMobileNonDesktopClient_NoDeeplinkScheme(
|
|
string clientName)
|
|
{
|
|
// Arrange
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Headers["Bitwarden-Client-Name"] = clientName;
|
|
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={clientName}", result);
|
|
Assert.DoesNotContain("deeplinkScheme", result);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildDuoTwoFactorRedirectUri_NoClientHeader_DefaultsToWeb()
|
|
{
|
|
// Arrange
|
|
var httpContext = new DefaultHttpContext();
|
|
// No Bitwarden-Client-Name header set
|
|
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=web", result);
|
|
Assert.DoesNotContain("deeplinkScheme", result);
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData("invalid-client")]
|
|
[BitAutoData("unknown")]
|
|
public void BuildDuoTwoFactorRedirectUri_InvalidClientHeader_DefaultsToWeb(
|
|
string invalidClientName)
|
|
{
|
|
// Arrange
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Headers["Bitwarden-Client-Name"] = invalidClientName;
|
|
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=web", result);
|
|
Assert.DoesNotContain("deeplinkScheme", result);
|
|
}
|
|
|
|
[Theory]
|
|
[BitAutoData("MOBILE")]
|
|
[BitAutoData("Mobile")]
|
|
[BitAutoData("MoBiLe")]
|
|
public void BuildDuoTwoFactorRedirectUri_ClientHeaderCaseInsensitive(
|
|
string clientName)
|
|
{
|
|
// Arrange
|
|
var httpContext = new DefaultHttpContext();
|
|
httpContext.Request.Headers["Bitwarden-Client-Name"] = clientName;
|
|
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);
|
|
}
|
|
}
|