1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-03 09:02:48 -05: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:
Ike
2024-01-24 10:13:00 -08:00
committed by GitHub
parent 7577da083c
commit 0deb13791a
8 changed files with 180 additions and 9 deletions

View 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;
}
}

View File

@ -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";

View File

@ -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" />