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(); } else if (providers.ContainsKey(TwoFactorProviderType.Authenticator)) { providers.Remove(TwoFactorProviderType.Authenticator); } providers.Add(TwoFactorProviderType.Authenticator, new TwoFactorProvider { MetaData = new Dictionary { ["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(); } else if (providers.ContainsKey(TwoFactorProviderType.Duo)) { providers.Remove(TwoFactorProviderType.Duo); } Temporary_SyncDuoParams(); providers.Add(TwoFactorProviderType.Duo, new TwoFactorProvider { MetaData = new Dictionary { //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(); } else if (providers.ContainsKey(TwoFactorProviderType.OrganizationDuo)) { providers.Remove(TwoFactorProviderType.OrganizationDuo); } Temporary_SyncDuoParams(); providers.Add(TwoFactorProviderType.OrganizationDuo, new TwoFactorProvider { MetaData = new Dictionary { //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 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(); } else if (providers.ContainsKey(TwoFactorProviderType.YubiKey)) { providers.Remove(TwoFactorProviderType.YubiKey); } providers.Add(TwoFactorProviderType.YubiKey, new TwoFactorProvider { MetaData = new Dictionary { ["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 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(); } else if (providers.ContainsKey(TwoFactorProviderType.Email)) { providers.Remove(TwoFactorProviderType.Email); } providers.Add(TwoFactorProviderType.Email, new TwoFactorProvider { MetaData = new Dictionary { ["Email"] = Email.ToLowerInvariant() }, Enabled = true }); existingUser.SetTwoFactorProviders(providers); return existingUser; } public override IEnumerable 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 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; } }