diff --git a/src/Api/Controllers/TwoFactorController.cs b/src/Api/Controllers/TwoFactorController.cs index 6adc60a03d..ac768dd2d8 100644 --- a/src/Api/Controllers/TwoFactorController.cs +++ b/src/Api/Controllers/TwoFactorController.cs @@ -77,8 +77,7 @@ namespace Bit.Api.Controllers [HttpPut("yubikey")] [HttpPost("yubikey")] - public async Task PutYubiKey( - [FromBody]UpdateTwoFactorYubicoOtpRequestModel model) + public async Task PutYubiKey([FromBody]UpdateTwoFactorYubicoOtpRequestModel model) { var user = await CheckPasswordAsync(model.MasterPasswordHash); model.ToUser(user); @@ -94,6 +93,25 @@ namespace Bit.Api.Controllers return response; } + [HttpPost("get-duo")] + public async Task GetDuo([FromBody]TwoFactorRequestModel model) + { + var user = await CheckPasswordAsync(model.MasterPasswordHash); + var response = new TwoFactorDuoResponseModel(user); + return response; + } + + [HttpPut("duo")] + [HttpPost("duo")] + public async Task PutDuo([FromBody]UpdateTwoFactorDuoRequestModel model) + { + var user = await CheckPasswordAsync(model.MasterPasswordHash); + model.ToUser(user); + await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Duo); + var response = new TwoFactorDuoResponseModel(user); + return response; + } + public async Task ValidateYubiKeyAsync(User user, string name, string value) { if(string.IsNullOrWhiteSpace(value) || value.Length == 12) @@ -148,12 +166,11 @@ namespace Bit.Api.Controllers [HttpPut("disable")] [HttpPost("disable")] - public async Task PutDisable([FromBody]TwoFactorProviderRequestModel model) + public async Task PutDisable([FromBody]TwoFactorProviderRequestModel model) { var user = await CheckPasswordAsync(model.MasterPasswordHash); await _userService.DisableTwoFactorProviderAsync(user, model.Type.Value); - - var response = new TwoFactorEmailResponseModel(user); + var response = new TwoFactorProviderResponseModel(model.Type.Value, user); return response; } diff --git a/src/Core/Models/Api/Request/TwoFactorRequestModels.cs b/src/Core/Models/Api/Request/TwoFactorRequestModels.cs index 2ff139a7a6..af34cc5386 100644 --- a/src/Core/Models/Api/Request/TwoFactorRequestModels.cs +++ b/src/Core/Models/Api/Request/TwoFactorRequestModels.cs @@ -2,6 +2,8 @@ using Bit.Core.Models.Table; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System; +using System.Linq; namespace Bit.Core.Models.Api { @@ -36,7 +38,7 @@ namespace Bit.Core.Models.Api } } - public class UpdateTwoFactorDuoRequestModel : TwoFactorRequestModel + public class UpdateTwoFactorDuoRequestModel : TwoFactorRequestModel, IValidatableObject { [Required] [StringLength(50)] @@ -47,6 +49,40 @@ namespace Bit.Core.Models.Api [Required] [StringLength(50)] public string Host { get; set; } + + public User ToUser(User extistingUser) + { + var providers = extistingUser.GetTwoFactorProviders(); + if(providers == null) + { + providers = new Dictionary(); + } + else if(providers.ContainsKey(TwoFactorProviderType.Duo)) + { + providers.Remove(TwoFactorProviderType.Duo); + } + + providers.Add(TwoFactorProviderType.Duo, new TwoFactorProvider + { + MetaData = new Dictionary + { + ["SKey"] = SecretKey, + ["IKey"] = IntegrationKey, + ["Host"] = Host + }, + Enabled = true + }); + extistingUser.SetTwoFactorProviders(providers); + return extistingUser; + } + + public IEnumerable Validate(ValidationContext validationContext) + { + if(!Host.StartsWith("api-") || !Host.EndsWith(".duosecurity.com") || Host.Count(s => s == '.') != 2) + { + yield return new ValidationResult("Host is invalid.", new string[] { nameof(Host) }); + } + } } public class UpdateTwoFactorYubicoOtpRequestModel : TwoFactorRequestModel, IValidatableObject diff --git a/src/Core/Models/Api/Response/TwoFactor/TwoFactorDuoResponseModel.cs b/src/Core/Models/Api/Response/TwoFactor/TwoFactorDuoResponseModel.cs new file mode 100644 index 0000000000..29674330cc --- /dev/null +++ b/src/Core/Models/Api/Response/TwoFactor/TwoFactorDuoResponseModel.cs @@ -0,0 +1,46 @@ +using System; +using Bit.Core.Enums; +using Bit.Core.Models.Table; + +namespace Bit.Core.Models.Api +{ + public class TwoFactorDuoResponseModel : ResponseModel + { + public TwoFactorDuoResponseModel(User user) + : base("twoFactorDuo") + { + if(user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); + if(provider?.MetaData != null && provider.MetaData.Count > 0) + { + Enabled = provider.Enabled; + + if(provider.MetaData.ContainsKey("Host")) + { + Host = provider.MetaData["Host"]; + } + if(provider.MetaData.ContainsKey("SKey")) + { + SecretKey = provider.MetaData["SKey"]; + } + if(provider.MetaData.ContainsKey("IKey")) + { + IntegrationKey = provider.MetaData["IKey"]; + } + } + else + { + Enabled = false; + } + } + + public bool Enabled { get; set; } + public string Host { get; set; } + public string SecretKey { get; set; } + public string IntegrationKey { get; set; } + } +} diff --git a/src/Core/Models/Api/Response/TwoFactor/TwoFactorProviderResponseModel.cs b/src/Core/Models/Api/Response/TwoFactor/TwoFactorProviderResponseModel.cs index aabb308ff0..519813b103 100644 --- a/src/Core/Models/Api/Response/TwoFactor/TwoFactorProviderResponseModel.cs +++ b/src/Core/Models/Api/Response/TwoFactor/TwoFactorProviderResponseModel.cs @@ -1,5 +1,6 @@ using System; using Bit.Core.Enums; +using Bit.Core.Models.Table; namespace Bit.Core.Models.Api { @@ -17,6 +18,19 @@ namespace Bit.Core.Models.Api Type = type; } + public TwoFactorProviderResponseModel(TwoFactorProviderType type, User user) + : base("twoFactorProvider") + { + if(user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + var provider = user.GetTwoFactorProvider(type); + Enabled = provider?.Enabled ?? false; + Type = type; + } + public bool Enabled { get; set; } public TwoFactorProviderType Type { get; set; } }