1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 07:36:14 -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

@ -0,0 +1,121 @@
using Bit.Api.Auth.Models.Request;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
using Xunit;
namespace Bit.Api.Test.Auth.Models.Request;
public class OrganizationTwoFactorDuoRequestModelTests
{
[Fact]
public void ShouldAddOrUpdateTwoFactorProvider_WhenExistingProviderDoesNotExist()
{
// Arrange
var existingOrg = new Organization();
var model = new UpdateTwoFactorDuoRequestModel
{
ClientId = "clientId",
ClientSecret = "clientSecret",
IntegrationKey = "integrationKey",
SecretKey = "secretKey",
Host = "example.com"
};
// Act
var result = model.ToOrganization(existingOrg);
// Assert
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.OrganizationDuo));
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientId"]);
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientSecret"]);
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["IKey"]);
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["SKey"]);
Assert.Equal("example.com", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["Host"]);
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].Enabled);
}
[Fact]
public void ShouldUpdateTwoFactorProvider_WhenExistingProviderExists()
{
// Arrange
var existingOrg = new Organization();
existingOrg.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
{ TwoFactorProviderType.OrganizationDuo, new TwoFactorProvider() }
});
var model = new UpdateTwoFactorDuoRequestModel
{
ClientId = "newClientId",
ClientSecret = "newClientSecret",
IntegrationKey = "newIntegrationKey",
SecretKey = "newSecretKey",
Host = "newExample.com"
};
// Act
var result = model.ToOrganization(existingOrg);
// Assert
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.OrganizationDuo));
Assert.Equal("newClientId", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientId"]);
Assert.Equal("newClientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientSecret"]);
Assert.Equal("newClientId", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["IKey"]);
Assert.Equal("newClientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["SKey"]);
Assert.Equal("newExample.com", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["Host"]);
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].Enabled);
}
[Fact]
public void DuoV2ParamsSync_WhenExistingProviderDoesNotExist()
{
// Arrange
var existingOrg = new Organization();
var model = new UpdateTwoFactorDuoRequestModel
{
IntegrationKey = "integrationKey",
SecretKey = "secretKey",
Host = "example.com"
};
// Act
var result = model.ToOrganization(existingOrg);
// Assert
// IKey and SKey should be the same as ClientId and ClientSecret
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.OrganizationDuo));
Assert.Equal("integrationKey", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientId"]);
Assert.Equal("secretKey", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientSecret"]);
Assert.Equal("integrationKey", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["IKey"]);
Assert.Equal("secretKey", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["SKey"]);
Assert.Equal("example.com", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["Host"]);
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].Enabled);
}
[Fact]
public void DuoV4ParamsSync_WhenExistingProviderDoesNotExist()
{
// Arrange
var existingOrg = new Organization();
var model = new UpdateTwoFactorDuoRequestModel
{
ClientId = "clientId",
ClientSecret = "clientSecret",
Host = "example.com"
};
// Act
var result = model.ToOrganization(existingOrg);
// Assert
// IKey and SKey should be the same as ClientId and ClientSecret
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.OrganizationDuo));
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientId"]);
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["ClientSecret"]);
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["IKey"]);
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["SKey"]);
Assert.Equal("example.com", result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].MetaData["Host"]);
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.OrganizationDuo].Enabled);
}
}

View File

@ -0,0 +1,67 @@
using System.ComponentModel.DataAnnotations;
using Bit.Api.Auth.Models.Request;
using Xunit;
namespace Bit.Api.Test.Auth.Models.Request;
public class TwoFactorDuoRequestModelValidationTests
{
[Fact]
public void ShouldReturnValidationError_WhenHostIsInvalid()
{
// Arrange
var model = new UpdateTwoFactorDuoRequestModel
{
Host = "invalidHost",
ClientId = "clientId",
ClientSecret = "clientSecret",
};
// Act
var result = model.Validate(new ValidationContext(model));
// Assert
Assert.Single(result);
Assert.Equal("Host is invalid.", result.First().ErrorMessage);
Assert.Equal("Host", result.First().MemberNames.First());
}
[Fact]
public void ShouldReturnValidationError_WhenValuesAreInvalid()
{
// Arrange
var model = new UpdateTwoFactorDuoRequestModel
{
Host = "api-12345abc.duosecurity.com"
};
// Act
var result = model.Validate(new ValidationContext(model));
// Assert
Assert.Single(result);
Assert.Equal("Neither v2 or v4 values are valid.", result.First().ErrorMessage);
Assert.Contains("ClientId", result.First().MemberNames);
Assert.Contains("ClientSecret", result.First().MemberNames);
Assert.Contains("IntegrationKey", result.First().MemberNames);
Assert.Contains("SecretKey", result.First().MemberNames);
}
[Fact]
public void ShouldReturnSuccess_WhenValuesAreValid()
{
// Arrange
var model = new UpdateTwoFactorDuoRequestModel
{
Host = "api-12345abc.duosecurity.com",
ClientId = "clientId",
ClientSecret = "clientSecret",
};
// Act
var result = model.Validate(new ValidationContext(model));
// Assert
Assert.Empty(result);
}
}

View File

@ -0,0 +1,122 @@
using Bit.Api.Auth.Models.Request;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
using Bit.Core.Entities;
using Xunit;
namespace Bit.Api.Test.Auth.Models.Request;
public class UserTwoFactorDuoRequestModelTests
{
[Fact]
public void ShouldAddOrUpdateTwoFactorProvider_WhenExistingProviderDoesNotExist()
{
// Arrange
var existingUser = new User();
var model = new UpdateTwoFactorDuoRequestModel
{
ClientId = "clientId",
ClientSecret = "clientSecret",
IntegrationKey = "integrationKey",
SecretKey = "secretKey",
Host = "example.com"
};
// Act
var result = model.ToUser(existingUser);
// Assert
// IKey and SKey should be the same as ClientId and ClientSecret
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.Duo));
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientId"]);
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientSecret"]);
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["IKey"]);
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["SKey"]);
Assert.Equal("example.com", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["Host"]);
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].Enabled);
}
[Fact]
public void ShouldUpdateTwoFactorProvider_WhenExistingProviderExists()
{
// Arrange
var existingUser = new User();
existingUser.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
{ TwoFactorProviderType.Duo, new TwoFactorProvider() }
});
var model = new UpdateTwoFactorDuoRequestModel
{
ClientId = "newClientId",
ClientSecret = "newClientSecret",
IntegrationKey = "newIntegrationKey",
SecretKey = "newSecretKey",
Host = "newExample.com"
};
// Act
var result = model.ToUser(existingUser);
// Assert
// IKey and SKey should be the same as ClientId and ClientSecret
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.Duo));
Assert.Equal("newClientId", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientId"]);
Assert.Equal("newClientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientSecret"]);
Assert.Equal("newClientId", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["IKey"]);
Assert.Equal("newClientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["SKey"]);
Assert.Equal("newExample.com", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["Host"]);
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].Enabled);
}
[Fact]
public void DuoV2ParamsSync_WhenExistingProviderDoesNotExist()
{
// Arrange
var existingUser = new User();
var model = new UpdateTwoFactorDuoRequestModel
{
IntegrationKey = "integrationKey",
SecretKey = "secretKey",
Host = "example.com"
};
// Act
var result = model.ToUser(existingUser);
// Assert
// IKey and SKey should be the same as ClientId and ClientSecret
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.Duo));
Assert.Equal("integrationKey", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientId"]);
Assert.Equal("secretKey", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientSecret"]);
Assert.Equal("integrationKey", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["IKey"]);
Assert.Equal("secretKey", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["SKey"]);
Assert.Equal("example.com", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["Host"]);
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].Enabled);
}
[Fact]
public void DuoV4ParamsSync_WhenExistingProviderDoesNotExist()
{
// Arrange
var existingUser = new User();
var model = new UpdateTwoFactorDuoRequestModel
{
ClientId = "clientId",
ClientSecret = "clientSecret",
Host = "example.com"
};
// Act
var result = model.ToUser(existingUser);
// Assert
// IKey and SKey should be the same as ClientId and ClientSecret
Assert.True(result.GetTwoFactorProviders().ContainsKey(TwoFactorProviderType.Duo));
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientId"]);
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["ClientSecret"]);
Assert.Equal("clientId", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["IKey"]);
Assert.Equal("clientSecret", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["SKey"]);
Assert.Equal("example.com", result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].MetaData["Host"]);
Assert.True(result.GetTwoFactorProviders()[TwoFactorProviderType.Duo].Enabled);
}
}

View File

@ -0,0 +1,107 @@

using Bit.Api.Auth.Models.Response.TwoFactor;
using Bit.Core.AdminConsole.Entities;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Api.Test.Auth.Models.Response;
public class OrganizationTwoFactorDuoResponseModelTests
{
[Theory]
[BitAutoData]
public void Organization_WithDuoV4_ShouldBuildModel(Organization organization)
{
// Arrange
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoV4ProvidersJson();
// Act
var model = new TwoFactorDuoResponseModel(organization);
// Assert if v4 data Ikey and Skey are set to clientId and clientSecret
Assert.NotNull(model);
Assert.Equal("clientId", model.ClientId);
Assert.Equal("clientSecret", model.ClientSecret);
Assert.Equal("clientId", model.IntegrationKey);
Assert.Equal("clientSecret", model.SecretKey);
}
[Theory]
[BitAutoData]
public void Organization_WithDuoV2_ShouldBuildModel(Organization organization)
{
// Arrange
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoV2ProvidersJson();
// Act
var model = new TwoFactorDuoResponseModel(organization);
// Assert if only v2 data clientId and clientSecret are set to Ikey and Sk
Assert.NotNull(model);
Assert.Equal("IKey", model.ClientId);
Assert.Equal("SKey", model.ClientSecret);
Assert.Equal("IKey", model.IntegrationKey);
Assert.Equal("SKey", model.SecretKey);
}
[Theory]
[BitAutoData]
public void Organization_WithDuo_ShouldBuildModel(Organization organization)
{
// Arrange
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProvidersJson();
// Act
var model = new TwoFactorDuoResponseModel(organization);
/// Assert Even if both versions are present priority is given to v4 data
Assert.NotNull(model);
Assert.Equal("clientId", model.ClientId);
Assert.Equal("clientSecret", model.ClientSecret);
Assert.Equal("clientId", model.IntegrationKey);
Assert.Equal("clientSecret", model.SecretKey);
}
[Theory]
[BitAutoData]
public void Organization_WithDuoEmpty_ShouldFail(Organization organization)
{
// Arrange
organization.TwoFactorProviders = "{\"6\" : {}}";
// Act
var model = new TwoFactorDuoResponseModel(organization);
/// Assert
Assert.False(model.Enabled);
}
[Theory]
[BitAutoData]
public void Organization_WithTwoFactorProvidersNull_ShouldFail(Organization organization)
{
// Arrange
organization.TwoFactorProviders = "{\"6\" : {}}";
// Act
var model = new TwoFactorDuoResponseModel(organization);
/// Assert
Assert.False(model.Enabled);
}
private string GetTwoFactorOrganizationDuoProvidersJson()
{
return "{\"6\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"ClientSecret\":\"clientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
}
private string GetTwoFactorOrganizationDuoV4ProvidersJson()
{
return "{\"6\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"clientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
}
private string GetTwoFactorOrganizationDuoV2ProvidersJson()
{
return "{\"6\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"Host\":\"example.com\"}}}";
}
}

View File

@ -0,0 +1,107 @@

using Bit.Api.Auth.Models.Response.TwoFactor;
using Bit.Core.Entities;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Api.Test.Auth.Models.Response;
public class UserTwoFactorDuoResponseModelTests
{
[Theory]
[BitAutoData]
public void User_WithDuoV4_ShouldBuildModel(User user)
{
// Arrange
user.TwoFactorProviders = GetTwoFactorDuoV4ProvidersJson();
// Act
var model = new TwoFactorDuoResponseModel(user);
// Assert if v4 data Ikey and Skey are set to clientId and clientSecret
Assert.NotNull(model);
Assert.Equal("clientId", model.ClientId);
Assert.Equal("clientSecret", model.ClientSecret);
Assert.Equal("clientId", model.IntegrationKey);
Assert.Equal("clientSecret", model.SecretKey);
}
[Theory]
[BitAutoData]
public void User_WithDuov2_ShouldBuildModel(User user)
{
// Arrange
user.TwoFactorProviders = GetTwoFactorDuoV2ProvidersJson();
// Act
var model = new TwoFactorDuoResponseModel(user);
// Assert if only v2 data clientId and clientSecret are set to Ikey and Skey
Assert.NotNull(model);
Assert.Equal("IKey", model.ClientId);
Assert.Equal("SKey", model.ClientSecret);
Assert.Equal("IKey", model.IntegrationKey);
Assert.Equal("SKey", model.SecretKey);
}
[Theory]
[BitAutoData]
public void User_WithDuo_ShouldBuildModel(User user)
{
// Arrange
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
// Act
var model = new TwoFactorDuoResponseModel(user);
// Assert Even if both versions are present priority is given to v4 data
Assert.NotNull(model);
Assert.Equal("clientId", model.ClientId);
Assert.Equal("clientSecret", model.ClientSecret);
Assert.Equal("clientId", model.IntegrationKey);
Assert.Equal("clientSecret", model.SecretKey);
}
[Theory]
[BitAutoData]
public void User_WithDuoEmpty_ShouldFail(User user)
{
// Arrange
user.TwoFactorProviders = "{\"2\" : {}}";
// Act
var model = new TwoFactorDuoResponseModel(user);
/// Assert
Assert.False(model.Enabled);
}
[Theory]
[BitAutoData]
public void User_WithTwoFactorProvidersNull_ShouldFail(User user)
{
// Arrange
user.TwoFactorProviders = null;
// Act
var model = new TwoFactorDuoResponseModel(user);
/// Assert
Assert.False(model.Enabled);
}
private string GetTwoFactorDuoProvidersJson()
{
return "{\"2\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"ClientSecret\":\"clientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
}
private string GetTwoFactorDuoV4ProvidersJson()
{
return "{\"2\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"clientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
}
private string GetTwoFactorDuoV2ProvidersJson()
{
return "{\"2\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"Host\":\"example.com\"}}}";
}
}