You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
398 lines
14 KiB
398 lines
14 KiB
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); |
|
} |
|
}
|
|
|