mirror of
https://github.com/bitwarden/server.git
synced 2025-02-23 03:01:23 +01:00
[PM-6303] Add duo state to 2fa (#3806)
* add duo state to 2fa * Id to UserId
This commit is contained in:
parent
744d21ec5e
commit
d99d3b8380
@ -1,12 +1,14 @@
|
||||
using Bit.Core.Auth.Models;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tokens;
|
||||
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
|
||||
@ -22,6 +24,7 @@ public class TemporaryDuoWebV4SDKService : ITemporaryDuoWebV4SDKService
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IDataProtectorTokenFactory<DuoUserStateTokenable> _tokenDataFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for the DuoUniversalPromptService. Used to supplement v2 implementation of Duo with v4 SDK
|
||||
@ -30,10 +33,12 @@ public class TemporaryDuoWebV4SDKService : ITemporaryDuoWebV4SDKService
|
||||
/// <param name="globalSettings">used to fetch vault URL for Redirect URL</param>
|
||||
public TemporaryDuoWebV4SDKService(
|
||||
ICurrentContext currentContext,
|
||||
GlobalSettings globalSettings)
|
||||
GlobalSettings globalSettings,
|
||||
IDataProtectorTokenFactory<DuoUserStateTokenable> tokenDataFactory)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
_globalSettings = globalSettings;
|
||||
_tokenDataFactory = tokenDataFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -56,7 +61,7 @@ public class TemporaryDuoWebV4SDKService : ITemporaryDuoWebV4SDKService
|
||||
return null;
|
||||
}
|
||||
|
||||
var state = Duo.Client.GenerateState(); //? Not sure on this yet. But required for GenerateAuthUrl
|
||||
var state = _tokenDataFactory.Protect(new DuoUserStateTokenable(user));
|
||||
var authUrl = duoClient.GenerateAuthUri(user.Email, state);
|
||||
|
||||
return authUrl;
|
||||
@ -82,8 +87,20 @@ public class TemporaryDuoWebV4SDKService : ITemporaryDuoWebV4SDKService
|
||||
return false;
|
||||
}
|
||||
|
||||
var parts = token.Split("|");
|
||||
var authCode = parts[0];
|
||||
var state = parts[1];
|
||||
|
||||
_tokenDataFactory.TryUnprotect(state, out var tokenable);
|
||||
if (!tokenable.Valid || !tokenable.TokenIsValid(user))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// duoClient compares the email from the received IdToken with user.Email to verify a bad actor hasn't used
|
||||
// their authCode with a victims credentials
|
||||
var res = await duoClient.ExchangeAuthorizationCodeFor2faResult(authCode, user.Email);
|
||||
// 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";
|
||||
}
|
||||
|
||||
@ -100,7 +117,7 @@ public class TemporaryDuoWebV4SDKService : ITemporaryDuoWebV4SDKService
|
||||
/// <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
|
||||
// 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}",
|
||||
|
@ -0,0 +1,37 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Tokens;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Core.Auth.Models.Business.Tokenables;
|
||||
|
||||
public class DuoUserStateTokenable : Tokenable
|
||||
{
|
||||
public const string ClearTextPrefix = "BwDuoUserId";
|
||||
public const string DataProtectorPurpose = "DuoUserIdTokenDataProtector";
|
||||
public const string TokenIdentifier = "DuoUserIdToken";
|
||||
public string Identifier { get; set; } = TokenIdentifier;
|
||||
public Guid UserId { get; set; }
|
||||
|
||||
public override bool Valid => Identifier == TokenIdentifier &&
|
||||
UserId != default;
|
||||
|
||||
[JsonConstructor]
|
||||
public DuoUserStateTokenable()
|
||||
{
|
||||
}
|
||||
|
||||
public DuoUserStateTokenable(User user)
|
||||
{
|
||||
UserId = user?.Id ?? default;
|
||||
}
|
||||
|
||||
public bool TokenIsValid(User user)
|
||||
{
|
||||
if (UserId == default || user == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return UserId == user.Id;
|
||||
}
|
||||
}
|
@ -197,6 +197,12 @@ public static class ServiceCollectionExtensions
|
||||
OrgUserInviteTokenable.DataProtectorPurpose,
|
||||
serviceProvider.GetDataProtectionProvider(),
|
||||
serviceProvider.GetRequiredService<ILogger<DataProtectorTokenFactory<OrgUserInviteTokenable>>>()));
|
||||
services.AddSingleton<IDataProtectorTokenFactory<DuoUserStateTokenable>>(serviceProvider =>
|
||||
new DataProtectorTokenFactory<DuoUserStateTokenable>(
|
||||
DuoUserStateTokenable.ClearTextPrefix,
|
||||
DuoUserStateTokenable.DataProtectorPurpose,
|
||||
serviceProvider.GetDataProtectionProvider(),
|
||||
serviceProvider.GetRequiredService<ILogger<DataProtectorTokenFactory<DuoUserStateTokenable>>>()));
|
||||
}
|
||||
|
||||
public static void AddDefaultServices(this IServiceCollection services, GlobalSettings globalSettings)
|
||||
|
Loading…
Reference in New Issue
Block a user