1
0
mirror of https://github.com/bitwarden/server synced 2025-12-17 00:33:23 +00:00
Files
server/src/Api/Auth/Models/Request/TwoFactorRequestModels.cs
Ike aba2f023cd [PM-9925] Tokenable for User Verification on Two Factor Authenticator settings (#4558)
* initial changes

* Fixing some bits

* fixing issue when feature flag is `false`; also names;

* consume OTP on read if FF true

* comment typo

* fix formatting

* check access code first to not consume token

* add docs

* revert checking access code first

* update error messages

* remove line number from comment

---------

Co-authored-by: Jake Fink <jfink@bitwarden.com>
2024-07-25 07:51:23 -07:00

334 lines
11 KiB
C#

using System.ComponentModel.DataAnnotations;
using Bit.Api.Auth.Models.Request.Accounts;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
using Bit.Core.Auth.Utilities;
using Bit.Core.Entities;
using Fido2NetLib;
namespace Bit.Api.Auth.Models.Request;
public class UpdateTwoFactorAuthenticatorRequestModel : SecretVerificationRequestModel
{
[Required]
[StringLength(50)]
public string Token { get; set; }
[Required]
[StringLength(50)]
public string Key { get; set; }
public string UserVerificationToken { get; set; }
public User ToUser(User existingUser)
{
var providers = existingUser.GetTwoFactorProviders();
if (providers == null)
{
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
}
else if (providers.ContainsKey(TwoFactorProviderType.Authenticator))
{
providers.Remove(TwoFactorProviderType.Authenticator);
}
providers.Add(TwoFactorProviderType.Authenticator, new TwoFactorProvider
{
MetaData = new Dictionary<string, object> { ["Key"] = Key },
Enabled = true
});
existingUser.SetTwoFactorProviders(providers);
return existingUser;
}
}
public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IValidatableObject
{
/*
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; }
//todo - will remove SKey and IKey with PM-8107
[StringLength(50)]
public string SecretKey { get; set; }
[Required]
[StringLength(50)]
public string Host { get; set; }
public User ToUser(User existingUser)
{
var providers = existingUser.GetTwoFactorProviders();
if (providers == null)
{
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
}
else if (providers.ContainsKey(TwoFactorProviderType.Duo))
{
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
});
existingUser.SetTwoFactorProviders(providers);
return existingUser;
}
public Organization ToOrganization(Organization existingOrg)
{
var providers = existingOrg.GetTwoFactorProviders();
if (providers == null)
{
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
}
else if (providers.ContainsKey(TwoFactorProviderType.OrganizationDuo))
{
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
});
existingOrg.SetTwoFactorProviders(providers);
return existingOrg;
}
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!DuoApi.ValidHost(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;
}
}
}
public class UpdateTwoFactorYubicoOtpRequestModel : SecretVerificationRequestModel, IValidatableObject
{
public string Key1 { get; set; }
public string Key2 { get; set; }
public string Key3 { get; set; }
public string Key4 { get; set; }
public string Key5 { get; set; }
[Required]
public bool? Nfc { get; set; }
public User ToUser(User existingUser)
{
var providers = existingUser.GetTwoFactorProviders();
if (providers == null)
{
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
}
else if (providers.ContainsKey(TwoFactorProviderType.YubiKey))
{
providers.Remove(TwoFactorProviderType.YubiKey);
}
providers.Add(TwoFactorProviderType.YubiKey, new TwoFactorProvider
{
MetaData = new Dictionary<string, object>
{
["Key1"] = FormatKey(Key1),
["Key2"] = FormatKey(Key2),
["Key3"] = FormatKey(Key3),
["Key4"] = FormatKey(Key4),
["Key5"] = FormatKey(Key5),
["Nfc"] = Nfc.Value
},
Enabled = true
});
existingUser.SetTwoFactorProviders(providers);
return existingUser;
}
private string FormatKey(string keyValue)
{
if (string.IsNullOrWhiteSpace(keyValue))
{
return null;
}
return keyValue.Substring(0, 12);
}
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrWhiteSpace(Key1) && string.IsNullOrWhiteSpace(Key2) && string.IsNullOrWhiteSpace(Key3) &&
string.IsNullOrWhiteSpace(Key4) && string.IsNullOrWhiteSpace(Key5))
{
yield return new ValidationResult("A key is required.", new string[] { nameof(Key1) });
}
if (!string.IsNullOrWhiteSpace(Key1) && Key1.Length < 12)
{
yield return new ValidationResult("Key 1 in invalid.", new string[] { nameof(Key1) });
}
if (!string.IsNullOrWhiteSpace(Key2) && Key2.Length < 12)
{
yield return new ValidationResult("Key 2 in invalid.", new string[] { nameof(Key2) });
}
if (!string.IsNullOrWhiteSpace(Key3) && Key3.Length < 12)
{
yield return new ValidationResult("Key 3 in invalid.", new string[] { nameof(Key3) });
}
if (!string.IsNullOrWhiteSpace(Key4) && Key4.Length < 12)
{
yield return new ValidationResult("Key 4 in invalid.", new string[] { nameof(Key4) });
}
if (!string.IsNullOrWhiteSpace(Key5) && Key5.Length < 12)
{
yield return new ValidationResult("Key 5 in invalid.", new string[] { nameof(Key5) });
}
}
}
public class TwoFactorEmailRequestModel : SecretVerificationRequestModel
{
[Required]
[EmailAddress]
[StringLength(256)]
public string Email { get; set; }
public string AuthRequestId { get; set; }
// An auth session token used for obtaining email and as an authN factor for the sending of emailed 2FA OTPs.
public string SsoEmail2FaSessionToken { get; set; }
public User ToUser(User existingUser)
{
var providers = existingUser.GetTwoFactorProviders();
if (providers == null)
{
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
}
else if (providers.ContainsKey(TwoFactorProviderType.Email))
{
providers.Remove(TwoFactorProviderType.Email);
}
providers.Add(TwoFactorProviderType.Email, new TwoFactorProvider
{
MetaData = new Dictionary<string, object> { ["Email"] = Email.ToLowerInvariant() },
Enabled = true
});
existingUser.SetTwoFactorProviders(providers);
return existingUser;
}
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (string.IsNullOrEmpty(Secret) && string.IsNullOrEmpty(AuthRequestAccessCode) && string.IsNullOrEmpty((SsoEmail2FaSessionToken)))
{
yield return new ValidationResult("MasterPasswordHash, OTP, AccessCode, or SsoEmail2faSessionToken must be supplied.");
}
}
}
public class TwoFactorWebAuthnRequestModel : TwoFactorWebAuthnDeleteRequestModel
{
[Required]
public AuthenticatorAttestationRawResponse DeviceResponse { get; set; }
public string Name { get; set; }
}
public class TwoFactorWebAuthnDeleteRequestModel : SecretVerificationRequestModel, IValidatableObject
{
[Required]
public int? Id { get; set; }
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
foreach (var validationResult in base.Validate(validationContext))
{
yield return validationResult;
}
if (!Id.HasValue || Id < 0 || Id > 5)
{
yield return new ValidationResult("Invalid Key Id", new string[] { nameof(Id) });
}
}
}
public class UpdateTwoFactorEmailRequestModel : TwoFactorEmailRequestModel
{
[Required]
[StringLength(50)]
public string Token { get; set; }
}
public class TwoFactorProviderRequestModel : SecretVerificationRequestModel
{
[Required]
public TwoFactorProviderType? Type { get; set; }
}
public class TwoFactorRecoveryRequestModel : TwoFactorEmailRequestModel
{
[Required]
[StringLength(32)]
public string RecoveryCode { get; set; }
}
public class TwoFactorAuthenticatorDisableRequestModel : TwoFactorProviderRequestModel
{
[Required]
public string UserVerificationToken { get; set; }
[Required]
public string Key { get; set; }
}