1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 00:22:50 -05:00

[PM-5216] User and Organization Duo Request and Response Model refactor (#4126)

* inital changes

* add provider GatewayType migrations

* db provider migrations

* removed duo migrations added v2 metadata to duo response

* removed helper scripts

* remove signature from org duo

* added backward compatibility for Duo v2

* added tests for duo request + response models

* refactors to TwoFactorController

* updated test methods to be compartmentalized by usage

* fix organization add duo

* Assert.Empty() fix for validator
This commit is contained in:
Ike
2024-06-05 11:42:02 -07:00
committed by GitHub
parent a0a7654077
commit 97b3f3e7ee
19 changed files with 8504 additions and 55 deletions

View File

@ -159,7 +159,16 @@ public class TwoFactorController : Controller
var user = await CheckAsync(model, true);
try
{
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
// for backwards compatibility - will be removed with PM-8107
DuoApi duoApi = null;
if (model.ClientId != null && model.ClientSecret != null)
{
duoApi = new DuoApi(model.ClientId, model.ClientSecret, model.Host);
}
else
{
duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
}
await duoApi.JSONApiCall("GET", "/auth/v2/check");
}
catch (DuoException)
@ -178,7 +187,7 @@ public class TwoFactorController : Controller
public async Task<TwoFactorDuoResponseModel> GetOrganizationDuo(string id,
[FromBody] SecretVerificationRequestModel model)
{
var user = await CheckAsync(model, false);
await CheckAsync(model, false);
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManagePolicies(orgIdGuid))
@ -186,12 +195,7 @@ public class TwoFactorController : Controller
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid) ?? throw new NotFoundException();
var response = new TwoFactorDuoResponseModel(organization);
return response;
}
@ -201,7 +205,7 @@ public class TwoFactorController : Controller
public async Task<TwoFactorDuoResponseModel> PutOrganizationDuo(string id,
[FromBody] UpdateTwoFactorDuoRequestModel model)
{
var user = await CheckAsync(model, false);
await CheckAsync(model, false);
var orgIdGuid = new Guid(id);
if (!await _currentContext.ManagePolicies(orgIdGuid))
@ -209,15 +213,19 @@ public class TwoFactorController : Controller
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid);
if (organization == null)
{
throw new NotFoundException();
}
var organization = await _organizationRepository.GetByIdAsync(orgIdGuid) ?? throw new NotFoundException();
try
{
var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
// for backwards compatibility - will be removed with PM-8107
DuoApi duoApi = null;
if (model.ClientId != null && model.ClientSecret != null)
{
duoApi = new DuoApi(model.ClientId, model.ClientSecret, model.Host);
}
else
{
duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host);
}
await duoApi.JSONApiCall("GET", "/auth/v2/check");
}
catch (DuoException)
@ -439,7 +447,7 @@ public class TwoFactorController : Controller
throw new BadRequestException(string.Empty, "User verification failed.");
}
if (premium && !(await _userService.CanAccessPremium(user)))
if (premium && !await _userService.CanAccessPremium(user))
{
throw new BadRequestException("Premium status is required.");
}

View File

@ -42,10 +42,18 @@ public class UpdateTwoFactorAuthenticatorRequestModel : SecretVerificationReques
public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IValidatableObject
{
[Required]
/*
To support both v2 and v4 we need to remove the required annotation from the properties.
todo - the required annotation will be added back in PM-8107.
*/
[StringLength(50)]
public string ClientId { get; set; }
[StringLength(50)]
public string ClientSecret { get; set; }
//todo - will remove SKey and IKey with PM-8107
[StringLength(50)]
public string IntegrationKey { get; set; }
[Required]
//todo - will remove SKey and IKey with PM-8107
[StringLength(50)]
public string SecretKey { get; set; }
[Required]
@ -64,12 +72,17 @@ public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IV
providers.Remove(TwoFactorProviderType.Duo);
}
Temporary_SyncDuoParams();
providers.Add(TwoFactorProviderType.Duo, new TwoFactorProvider
{
MetaData = new Dictionary<string, object>
{
//todo - will remove SKey and IKey with PM-8107
["SKey"] = SecretKey,
["IKey"] = IntegrationKey,
["ClientSecret"] = ClientSecret,
["ClientId"] = ClientId,
["Host"] = Host
},
Enabled = true
@ -90,12 +103,17 @@ public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IV
providers.Remove(TwoFactorProviderType.OrganizationDuo);
}
Temporary_SyncDuoParams();
providers.Add(TwoFactorProviderType.OrganizationDuo, new TwoFactorProvider
{
MetaData = new Dictionary<string, object>
{
//todo - will remove SKey and IKey with PM-8107
["SKey"] = SecretKey,
["IKey"] = IntegrationKey,
["ClientSecret"] = ClientSecret,
["ClientId"] = ClientId,
["Host"] = Host
},
Enabled = true
@ -108,7 +126,31 @@ public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IV
{
if (!DuoApi.ValidHost(Host))
{
yield return new ValidationResult("Host is invalid.", new string[] { nameof(Host) });
yield return new ValidationResult("Host is invalid.", [nameof(Host)]);
}
if (string.IsNullOrWhiteSpace(ClientSecret) && string.IsNullOrWhiteSpace(ClientId) &&
string.IsNullOrWhiteSpace(SecretKey) && string.IsNullOrWhiteSpace(IntegrationKey))
{
yield return new ValidationResult("Neither v2 or v4 values are valid.", [nameof(IntegrationKey), nameof(SecretKey), nameof(ClientSecret), nameof(ClientId)]);
}
}
/*
use this method to ensure that both v2 params and v4 params are in sync
todo will be removed in pm-8107
*/
private void Temporary_SyncDuoParams()
{
// Even if IKey and SKey exist prioritize v4 params ClientId and ClientSecret
if (!string.IsNullOrWhiteSpace(ClientSecret) && !string.IsNullOrWhiteSpace(ClientId))
{
SecretKey = ClientSecret;
IntegrationKey = ClientId;
}
else if (!string.IsNullOrWhiteSpace(SecretKey) && !string.IsNullOrWhiteSpace(IntegrationKey))
{
ClientSecret = SecretKey;
ClientId = IntegrationKey;
}
}
}

View File

@ -36,26 +36,54 @@ public class TwoFactorDuoResponseModel : ResponseModel
public bool Enabled { get; set; }
public string Host { get; set; }
//TODO - will remove SecretKey with PM-8107
public string SecretKey { get; set; }
//TODO - will remove IntegrationKey with PM-8107
public string IntegrationKey { get; set; }
public string ClientSecret { get; set; }
public string ClientId { get; set; }
// updated build to assist in the EDD migration for the Duo 2FA provider
private void Build(TwoFactorProvider provider)
{
if (provider?.MetaData != null && provider.MetaData.Count > 0)
{
Enabled = provider.Enabled;
if (provider.MetaData.ContainsKey("Host"))
if (provider.MetaData.TryGetValue("Host", out var host))
{
Host = (string)provider.MetaData["Host"];
Host = (string)host;
}
if (provider.MetaData.ContainsKey("SKey"))
//todo - will remove SKey and IKey with PM-8107
// check Skey and IKey first if they exist
if (provider.MetaData.TryGetValue("SKey", out var sKey))
{
SecretKey = (string)provider.MetaData["SKey"];
ClientSecret = (string)sKey;
SecretKey = (string)sKey;
}
if (provider.MetaData.ContainsKey("IKey"))
if (provider.MetaData.TryGetValue("IKey", out var iKey))
{
IntegrationKey = (string)provider.MetaData["IKey"];
IntegrationKey = (string)iKey;
ClientId = (string)iKey;
}
// Even if IKey and SKey exist prioritize v4 params ClientId and ClientSecret
if (provider.MetaData.TryGetValue("ClientSecret", out var clientSecret))
{
if (!string.IsNullOrWhiteSpace((string)clientSecret))
{
ClientSecret = (string)clientSecret;
SecretKey = (string)clientSecret;
}
}
if (provider.MetaData.TryGetValue("ClientId", out var clientId))
{
if (!string.IsNullOrWhiteSpace((string)clientId))
{
ClientId = (string)clientId;
IntegrationKey = (string)clientId;
}
}
}
else
@ -63,4 +91,27 @@ public class TwoFactorDuoResponseModel : ResponseModel
Enabled = false;
}
}
/*
use this method to ensure that both v2 params and v4 params are in sync
todo will be removed in pm-8107
*/
private void Temporary_SyncDuoParams()
{
// Even if IKey and SKey exist prioritize v4 params ClientId and ClientSecret
if (!string.IsNullOrWhiteSpace(ClientSecret) && !string.IsNullOrWhiteSpace(ClientId))
{
SecretKey = ClientSecret;
IntegrationKey = ClientId;
}
else if (!string.IsNullOrWhiteSpace(SecretKey) && !string.IsNullOrWhiteSpace(IntegrationKey))
{
ClientSecret = SecretKey;
ClientId = IntegrationKey;
}
else
{
throw new InvalidDataException("Invalid Duo parameters.");
}
}
}