mirror of
https://github.com/bitwarden/server.git
synced 2024-11-22 12:15:36 +01:00
[PM-4614] Updating Duo to SDK v4 for Universal Prompt (#3664)
* added v4 updates * Fixed packages. * Null checks and OrganizationDuo * enable backwards compatibility support * updated validation * Update DuoUniversalPromptService.cs add JIRA ticket for cleanup * Update BaseRequestValidator.cs * updates to names and comments * fixed tests * fixed validation errros and authURL * updated naming * Filename change * Update BaseRequestValidator.cs
This commit is contained in:
parent
7577da083c
commit
0deb13791a
121
src/Core/Auth/Identity/TemporaryDuoWebV4SDKService.cs
Normal file
121
src/Core/Auth/Identity/TemporaryDuoWebV4SDKService.cs
Normal file
@ -0,0 +1,121 @@
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Settings;
|
||||
using Duo = DuoUniversal;
|
||||
|
||||
namespace Bit.Core.Auth.Identity;
|
||||
|
||||
/*
|
||||
PM-5156 addresses tech debt
|
||||
Interface to allow for DI, will end up being removed as part of the removal of the old Duo SDK v2 flows.
|
||||
This service is to support SDK v4 flows for Duo. At some time in the future we will need
|
||||
to combine this service with the DuoWebTokenProvider and OrganizationDuoWebTokenProvider to support SDK v4.
|
||||
*/
|
||||
public interface ITemporaryDuoWebV4SDKService
|
||||
{
|
||||
Task<string> GenerateAsync(TwoFactorProvider provider, User user);
|
||||
Task<bool> ValidateAsync(string token, TwoFactorProvider provider, User user);
|
||||
}
|
||||
|
||||
public class TemporaryDuoWebV4SDKService : ITemporaryDuoWebV4SDKService
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the DuoUniversalPromptService. Used to supplement v2 implementation of Duo with v4 SDK
|
||||
/// </summary>
|
||||
/// <param name="currentContext">used to fetch initiating Client</param>
|
||||
/// <param name="globalSettings">used to fetch vault URL for Redirect URL</param>
|
||||
public TemporaryDuoWebV4SDKService(
|
||||
ICurrentContext currentContext,
|
||||
GlobalSettings globalSettings)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_globalSettings = globalSettings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provider agnostic (either Duo or OrganizationDuo) method to generate a Duo Auth URL
|
||||
/// </summary>
|
||||
/// <param name="provider">Either Duo or OrganizationDuo</param>
|
||||
/// <param name="user">self</param>
|
||||
/// <returns>AuthUrl for DUO SDK v4</returns>
|
||||
public async Task<string> GenerateAsync(TwoFactorProvider provider, User user)
|
||||
{
|
||||
if (!HasProperMetaData(provider))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var duoClient = await BuildDuoClientAsync(provider);
|
||||
if (duoClient == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var state = Duo.Client.GenerateState(); //? Not sure on this yet. But required for GenerateAuthUrl
|
||||
var authUrl = duoClient.GenerateAuthUri(user.Email, state);
|
||||
|
||||
return authUrl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates Duo SDK v4 response
|
||||
/// </summary>
|
||||
/// <param name="token">response form Duo</param>
|
||||
/// <param name="provider">TwoFactorProviderType Duo or OrganizationDuo</param>
|
||||
/// <param name="user">self</param>
|
||||
/// <returns>true or false depending on result of verification</returns>
|
||||
public async Task<bool> ValidateAsync(string token, TwoFactorProvider provider, User user)
|
||||
{
|
||||
if (!HasProperMetaData(provider))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var duoClient = await BuildDuoClientAsync(provider);
|
||||
if (duoClient == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the result of the exchange doesn't throw an exception and it's not null, then it's valid
|
||||
var res = await duoClient.ExchangeAuthorizationCodeFor2faResult(token, user.Email);
|
||||
return res.AuthResult.Result == "allow";
|
||||
}
|
||||
|
||||
private bool HasProperMetaData(TwoFactorProvider provider)
|
||||
{
|
||||
return provider?.MetaData != null && provider.MetaData.ContainsKey("IKey") &&
|
||||
provider.MetaData.ContainsKey("SKey") && provider.MetaData.ContainsKey("Host");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a Duo.Client object for use with Duo SDK v4. This combines the health check and the client generation
|
||||
/// </summary>
|
||||
/// <param name="provider">TwoFactorProvider Duo or OrganizationDuo</param>
|
||||
/// <returns>Duo.Client object or null</returns>
|
||||
private async Task<Duo.Client> BuildDuoClientAsync(TwoFactorProvider provider)
|
||||
{
|
||||
// Fetch Client name from header value since duo auth can be initiated from multiple clients and we want
|
||||
// to redirect back to the correct client
|
||||
_currentContext.HttpContext.Request.Headers.TryGetValue("Bitwarden-Client-Name", out var bitwardenClientName);
|
||||
var redirectUri = string.Format("{0}/duo-redirect-connector.html?client={1}",
|
||||
_globalSettings.BaseServiceUri.Vault, bitwardenClientName.FirstOrDefault() ?? "web");
|
||||
|
||||
var client = new Duo.ClientBuilder(
|
||||
(string)provider.MetaData["IKey"],
|
||||
(string)provider.MetaData["SKey"],
|
||||
(string)provider.MetaData["Host"],
|
||||
redirectUri).Build();
|
||||
|
||||
if (!await client.DoHealthCheck())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
}
|
@ -105,6 +105,7 @@ public static class FeatureFlagKeys
|
||||
public const string AutofillOverlay = "autofill-overlay";
|
||||
public const string ItemShare = "item-share";
|
||||
public const string KeyRotationImprovements = "key-rotation-improvements";
|
||||
public const string DuoRedirect = "duo-redirect";
|
||||
public const string FlexibleCollectionsMigration = "flexible-collections-migration";
|
||||
public const string FlexibleCollectionsSignup = "flexible-collections-signup";
|
||||
|
||||
|
@ -28,6 +28,7 @@
|
||||
<PackageReference Include="Azure.Storage.Blobs" Version="12.14.1" />
|
||||
<PackageReference Include="Azure.Storage.Queues" Version="12.12.0" />
|
||||
<PackageReference Include="BitPay.Light" Version="1.0.1907" />
|
||||
<PackageReference Include="DuoUniversal" Version="1.2.1" />
|
||||
<PackageReference Include="DnsClient" Version="1.7.0" />
|
||||
<PackageReference Include="Fido2.AspNet" Version="3.0.1" />
|
||||
<PackageReference Include="Handlebars.Net" Version="2.1.4" />
|
||||
|
@ -38,6 +38,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
private readonly IDeviceService _deviceService;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IOrganizationDuoWebTokenProvider _organizationDuoWebTokenProvider;
|
||||
private readonly ITemporaryDuoWebV4SDKService _duoWebV4SDKService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
@ -63,6 +64,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
IUserService userService,
|
||||
IEventService eventService,
|
||||
IOrganizationDuoWebTokenProvider organizationDuoWebTokenProvider,
|
||||
ITemporaryDuoWebV4SDKService duoWebV4SDKService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
@ -84,6 +86,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
_userService = userService;
|
||||
_eventService = eventService;
|
||||
_organizationDuoWebTokenProvider = organizationDuoWebTokenProvider;
|
||||
_duoWebV4SDKService = duoWebV4SDKService;
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_applicationCacheService = applicationCacheService;
|
||||
@ -167,7 +170,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
}
|
||||
return;
|
||||
}
|
||||
// We only want to track TOTPs in the chache to enforce one time use.
|
||||
// We only want to track TOTPs in the cache to enforce one time use.
|
||||
if (twoFactorProviderType == TwoFactorProviderType.Authenticator || twoFactorProviderType == TwoFactorProviderType.Email)
|
||||
{
|
||||
await Core.Utilities.DistributedCacheExtensions.SetAsync(_distributedCache, cacheKey, twoFactorToken, _cacheEntryOptions);
|
||||
@ -428,10 +431,23 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
case TwoFactorProviderType.WebAuthn:
|
||||
case TwoFactorProviderType.Remember:
|
||||
if (type != TwoFactorProviderType.Remember &&
|
||||
!(await _userService.TwoFactorProviderIsEnabledAsync(type, user)))
|
||||
!await _userService.TwoFactorProviderIsEnabledAsync(type, user))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// DUO SDK v4 Update: try to validate the token - PM-5156 addresses tech debt
|
||||
if (FeatureService.IsEnabled(FeatureFlagKeys.DuoRedirect))
|
||||
{
|
||||
if (type == TwoFactorProviderType.Duo)
|
||||
{
|
||||
if (!token.Contains(':'))
|
||||
{
|
||||
// We have to send the provider to the DuoWebV4SDKService to create the DuoClient
|
||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo);
|
||||
return await _duoWebV4SDKService.ValidateAsync(token, provider, user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await _userManager.VerifyTwoFactorTokenAsync(user,
|
||||
CoreHelpers.CustomProviderName(type), token);
|
||||
@ -441,6 +457,20 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
return false;
|
||||
}
|
||||
|
||||
// DUO SDK v4 Update: try to validate the token - PM-5156 addresses tech debt
|
||||
if (FeatureService.IsEnabled(FeatureFlagKeys.DuoRedirect))
|
||||
{
|
||||
if (type == TwoFactorProviderType.OrganizationDuo)
|
||||
{
|
||||
if (!token.Contains(':'))
|
||||
{
|
||||
// We have to send the provider to the DuoWebV4SDKService to create the DuoClient
|
||||
var provider = organization.GetTwoFactorProvider(TwoFactorProviderType.OrganizationDuo);
|
||||
return await _duoWebV4SDKService.ValidateAsync(token, provider, user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await _organizationDuoWebTokenProvider.ValidateAsync(token, organization, user);
|
||||
default:
|
||||
return false;
|
||||
@ -465,11 +495,19 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
CoreHelpers.CustomProviderName(type));
|
||||
if (type == TwoFactorProviderType.Duo)
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
var duoResponse = new Dictionary<string, object>
|
||||
{
|
||||
["Host"] = provider.MetaData["Host"],
|
||||
["Signature"] = token
|
||||
};
|
||||
|
||||
// DUO SDK v4 Update: Duo-Redirect
|
||||
if (FeatureService.IsEnabled(FeatureFlagKeys.DuoRedirect))
|
||||
{
|
||||
// Generate AuthUrl from DUO SDK v4 token provider
|
||||
duoResponse.Add("AuthUrl", await _duoWebV4SDKService.GenerateAsync(provider, user));
|
||||
}
|
||||
return duoResponse;
|
||||
}
|
||||
else if (type == TwoFactorProviderType.WebAuthn)
|
||||
{
|
||||
@ -493,13 +531,19 @@ public abstract class BaseRequestValidator<T> where T : class
|
||||
case TwoFactorProviderType.OrganizationDuo:
|
||||
if (await _organizationDuoWebTokenProvider.CanGenerateTwoFactorTokenAsync(organization))
|
||||
{
|
||||
return new Dictionary<string, object>
|
||||
var duoResponse = new Dictionary<string, object>
|
||||
{
|
||||
["Host"] = provider.MetaData["Host"],
|
||||
["Signature"] = await _organizationDuoWebTokenProvider.GenerateAsync(organization, user)
|
||||
};
|
||||
// DUO SDK v4 Update: DUO-Redirect
|
||||
if (FeatureService.IsEnabled(FeatureFlagKeys.DuoRedirect))
|
||||
{
|
||||
// Generate AuthUrl from DUO SDK v4 token provider
|
||||
duoResponse.Add("AuthUrl", await _duoWebV4SDKService.GenerateAsync(provider, user));
|
||||
}
|
||||
return duoResponse;
|
||||
}
|
||||
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
|
@ -33,6 +33,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
||||
IUserService userService,
|
||||
IEventService eventService,
|
||||
IOrganizationDuoWebTokenProvider organizationDuoWebTokenProvider,
|
||||
ITemporaryDuoWebV4SDKService duoWebV4SDKService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
@ -48,7 +49,7 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
|
||||
IDistributedCache distributedCache,
|
||||
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder)
|
||||
: base(userManager, deviceRepository, deviceService, userService, eventService,
|
||||
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
|
||||
organizationDuoWebTokenProvider, duoWebV4SDKService, organizationRepository, organizationUserRepository,
|
||||
applicationCacheService, mailService, logger, currentContext, globalSettings,
|
||||
userRepository, policyService, tokenDataFactory, featureService, ssoConfigRepository,
|
||||
distributedCache, userDecryptionOptionsBuilder)
|
||||
|
@ -34,6 +34,7 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
|
||||
IUserService userService,
|
||||
IEventService eventService,
|
||||
IOrganizationDuoWebTokenProvider organizationDuoWebTokenProvider,
|
||||
ITemporaryDuoWebV4SDKService duoWebV4SDKService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
@ -51,7 +52,7 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
|
||||
IDistributedCache distributedCache,
|
||||
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder)
|
||||
: base(userManager, deviceRepository, deviceService, userService, eventService,
|
||||
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
|
||||
organizationDuoWebTokenProvider, duoWebV4SDKService, organizationRepository, organizationUserRepository,
|
||||
applicationCacheService, mailService, logger, currentContext, globalSettings, userRepository, policyService,
|
||||
tokenDataFactory, featureService, ssoConfigRepository, distributedCache, userDecryptionOptionsBuilder)
|
||||
{
|
||||
|
@ -36,6 +36,7 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
|
||||
IUserService userService,
|
||||
IEventService eventService,
|
||||
IOrganizationDuoWebTokenProvider organizationDuoWebTokenProvider,
|
||||
ITemporaryDuoWebV4SDKService duoWebV4SDKService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
@ -54,7 +55,7 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
|
||||
IAssertWebAuthnLoginCredentialCommand assertWebAuthnLoginCredentialCommand
|
||||
)
|
||||
: base(userManager, deviceRepository, deviceService, userService, eventService,
|
||||
organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository,
|
||||
organizationDuoWebTokenProvider, duoWebV4SDKService, organizationRepository, organizationUserRepository,
|
||||
applicationCacheService, mailService, logger, currentContext, globalSettings,
|
||||
userRepository, policyService, tokenDataFactory, featureService, ssoConfigRepository, distributedCache, userDecryptionOptionsBuilder)
|
||||
{
|
||||
|
@ -376,7 +376,8 @@ public static class ServiceCollectionExtensions
|
||||
public static IdentityBuilder AddCustomIdentityServices(
|
||||
this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
services.AddSingleton<IOrganizationDuoWebTokenProvider, OrganizationDuoWebTokenProvider>();
|
||||
services.AddScoped<IOrganizationDuoWebTokenProvider, OrganizationDuoWebTokenProvider>();
|
||||
services.AddScoped<ITemporaryDuoWebV4SDKService, TemporaryDuoWebV4SDKService>();
|
||||
services.Configure<PasswordHasherOptions>(options => options.IterationCount = 100000);
|
||||
services.Configure<TwoFactorRememberTokenProviderOptions>(options =>
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user