From 0d4ea5ce5bac2b92f9ebe77d1e00d9129b6ba9ad Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Mon, 2 Apr 2018 23:18:26 -0400 Subject: [PATCH] organization 2fa apis --- src/Api/Controllers/TwoFactorController.cs | 107 +++++++++++++++++- src/Core/Enums/TwoFactorProviderType.cs | 3 +- .../Api/Request/TwoFactorRequestModels.cs | 29 ++++- .../TwoFactor/TwoFactorDuoResponseModel.cs | 31 ++++- .../TwoFactorProviderResponseModel.cs | 19 +++- src/Core/Services/IOrganizationService.cs | 2 + .../Implementations/OrganizationService.cs | 41 +++++++ 7 files changed, 219 insertions(+), 13 deletions(-) diff --git a/src/Api/Controllers/TwoFactorController.cs b/src/Api/Controllers/TwoFactorController.cs index 6d3bdb3ef9..5673095d20 100644 --- a/src/Api/Controllers/TwoFactorController.cs +++ b/src/Api/Controllers/TwoFactorController.cs @@ -10,6 +10,7 @@ using Bit.Core.Models.Table; using Bit.Core.Enums; using System.Linq; using Bit.Core; +using Bit.Core.Repositories; namespace Bit.Api.Controllers { @@ -18,17 +19,26 @@ namespace Bit.Api.Controllers public class TwoFactorController : Controller { private readonly IUserService _userService; + private readonly IOrganizationRepository _organizationRepository; + private readonly IOrganizationService _organizationService; private readonly GlobalSettings _globalSettings; private readonly UserManager _userManager; + private readonly CurrentContext _currentContext; public TwoFactorController( IUserService userService, + IOrganizationRepository organizationRepository, + IOrganizationService organizationService, GlobalSettings globalSettings, - UserManager userManager) + UserManager userManager, + CurrentContext currentContext) { _userService = userService; + _organizationRepository = organizationRepository; + _organizationService = organizationService; _globalSettings = globalSettings; _userManager = userManager; + _currentContext = currentContext; } [HttpGet("")] @@ -40,7 +50,28 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } - var providers = user.GetTwoFactorProviders()?.Select(p => new TwoFactorProviderResponseModel(p.Key, p.Value)); + var providers = user.GetTwoFactorProviders()?.Select( + p => new TwoFactorProviderResponseModel(p.Key, p.Value)); + return new ListResponseModel(providers); + } + + [HttpGet("~/organizations/{id}/two-factor")] + public async Task> GetOrganization(string id) + { + var orgIdGuid = new Guid(id); + if(!_currentContext.OrganizationAdmin(orgIdGuid)) + { + throw new NotFoundException(); + } + + var organization = await _organizationRepository.GetByIdAsync(orgIdGuid); + if(organization == null) + { + throw new NotFoundException(); + } + + var providers = organization.GetTwoFactorProviders()?.Select( + p => new TwoFactorProviderResponseModel(p.Key, p.Value)); return new ListResponseModel(providers); } @@ -116,6 +147,54 @@ namespace Bit.Api.Controllers return response; } + [HttpPost("~/organizations/{id}/two-factor/get-duo")] + public async Task GetOrganizationDuo(string id, + [FromBody]TwoFactorRequestModel model) + { + var user = await CheckAsync(model.MasterPasswordHash, false); + + var orgIdGuid = new Guid(id); + if(!_currentContext.OrganizationAdmin(orgIdGuid)) + { + throw new NotFoundException(); + } + + var organization = await _organizationRepository.GetByIdAsync(orgIdGuid); + if(organization == null) + { + throw new NotFoundException(); + } + + var response = new TwoFactorDuoResponseModel(organization); + return response; + } + + [HttpPut("~/organizations/{id}/two-factor/duo")] + [HttpPost("~/organizations/{id}/two-factor/duo")] + public async Task PutOrganizationDuo(string id, + [FromBody]UpdateTwoFactorDuoRequestModel model) + { + var user = await CheckAsync(model.MasterPasswordHash, false); + + var orgIdGuid = new Guid(id); + if(!_currentContext.OrganizationAdmin(orgIdGuid)) + { + throw new NotFoundException(); + } + + var organization = await _organizationRepository.GetByIdAsync(orgIdGuid); + if(organization == null) + { + throw new NotFoundException(); + } + + model.ToOrganization(organization); + await _organizationService.UpdateTwoFactorProviderAsync(organization, + TwoFactorProviderType.OrganizationDuo); + var response = new TwoFactorDuoResponseModel(organization); + return response; + } + [HttpPost("get-u2f")] public async Task GetU2f([FromBody]TwoFactorRequestModel model) { @@ -205,6 +284,30 @@ namespace Bit.Api.Controllers var response = new TwoFactorProviderResponseModel(model.Type.Value, user); return response; } + + [HttpPut("~/organizations/{id}/two-factor/disable")] + [HttpPost("~/organizations/{id}/two-factor/disable")] + public async Task PutOrganizationDisable(string id, + [FromBody]TwoFactorProviderRequestModel model) + { + var user = await CheckAsync(model.MasterPasswordHash, false); + + var orgIdGuid = new Guid(id); + if(!_currentContext.OrganizationAdmin(orgIdGuid)) + { + throw new NotFoundException(); + } + + var organization = await _organizationRepository.GetByIdAsync(orgIdGuid); + if(organization == null) + { + throw new NotFoundException(); + } + + await _organizationService.DisableTwoFactorProviderAsync(organization, model.Type.Value); + var response = new TwoFactorProviderResponseModel(model.Type.Value, organization); + return response; + } [HttpPost("get-recover")] public async Task GetRecover([FromBody]TwoFactorRequestModel model) diff --git a/src/Core/Enums/TwoFactorProviderType.cs b/src/Core/Enums/TwoFactorProviderType.cs index 28be1b2c1b..1a1b7ffc94 100644 --- a/src/Core/Enums/TwoFactorProviderType.cs +++ b/src/Core/Enums/TwoFactorProviderType.cs @@ -7,6 +7,7 @@ Duo = 2, YubiKey = 3, U2f = 4, - Remember = 5 + Remember = 5, + OrganizationDuo = 6 } } diff --git a/src/Core/Models/Api/Request/TwoFactorRequestModels.cs b/src/Core/Models/Api/Request/TwoFactorRequestModels.cs index 34293ea1a4..59df738508 100644 --- a/src/Core/Models/Api/Request/TwoFactorRequestModels.cs +++ b/src/Core/Models/Api/Request/TwoFactorRequestModels.cs @@ -2,7 +2,6 @@ using Bit.Core.Models.Table; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System; using System.Linq; namespace Bit.Core.Models.Api @@ -76,6 +75,32 @@ namespace Bit.Core.Models.Api return extistingUser; } + public Organization ToOrganization(Organization extistingOrg) + { + var providers = extistingOrg.GetTwoFactorProviders(); + if(providers == null) + { + providers = new Dictionary(); + } + else if(providers.ContainsKey(TwoFactorProviderType.OrganizationDuo)) + { + providers.Remove(TwoFactorProviderType.OrganizationDuo); + } + + providers.Add(TwoFactorProviderType.OrganizationDuo, new TwoFactorProvider + { + MetaData = new Dictionary + { + ["SKey"] = SecretKey, + ["IKey"] = IntegrationKey, + ["Host"] = Host + }, + Enabled = true + }); + extistingOrg.SetTwoFactorProviders(providers); + return extistingOrg; + } + public IEnumerable Validate(ValidationContext validationContext) { if(!Host.StartsWith("api-") || !Host.EndsWith(".duosecurity.com") || Host.Count(s => s == '.') != 2) @@ -214,7 +239,7 @@ namespace Bit.Core.Models.Api public class TwoFactorProviderRequestModel : TwoFactorRequestModel { [Required] - public Enums.TwoFactorProviderType? Type { get; set; } + public TwoFactorProviderType? Type { get; set; } } public class TwoFactorRequestModel diff --git a/src/Core/Models/Api/Response/TwoFactor/TwoFactorDuoResponseModel.cs b/src/Core/Models/Api/Response/TwoFactor/TwoFactorDuoResponseModel.cs index e370f9e5c7..7de73014aa 100644 --- a/src/Core/Models/Api/Response/TwoFactor/TwoFactorDuoResponseModel.cs +++ b/src/Core/Models/Api/Response/TwoFactor/TwoFactorDuoResponseModel.cs @@ -6,8 +6,10 @@ namespace Bit.Core.Models.Api { public class TwoFactorDuoResponseModel : ResponseModel { + private const string ResponseObj = "twoFactorDuo"; + public TwoFactorDuoResponseModel(User user) - : base("twoFactorDuo") + : base(ResponseObj) { if(user == null) { @@ -15,6 +17,28 @@ namespace Bit.Core.Models.Api } var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); + Build(provider); + } + + public TwoFactorDuoResponseModel(Organization org) + : base(ResponseObj) + { + if(org == null) + { + throw new ArgumentNullException(nameof(org)); + } + + var provider = org.GetTwoFactorProvider(TwoFactorProviderType.OrganizationDuo); + Build(provider); + } + + public bool Enabled { get; set; } + public string Host { get; set; } + public string SecretKey { get; set; } + public string IntegrationKey { get; set; } + + private void Build(TwoFactorProvider provider) + { if(provider?.MetaData != null && provider.MetaData.Count > 0) { Enabled = provider.Enabled; @@ -37,10 +61,5 @@ namespace Bit.Core.Models.Api 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 519813b103..15eec24ccc 100644 --- a/src/Core/Models/Api/Response/TwoFactor/TwoFactorProviderResponseModel.cs +++ b/src/Core/Models/Api/Response/TwoFactor/TwoFactorProviderResponseModel.cs @@ -6,8 +6,10 @@ namespace Bit.Core.Models.Api { public class TwoFactorProviderResponseModel : ResponseModel { + private const string ResponseObj = "twoFactorProvider"; + public TwoFactorProviderResponseModel(TwoFactorProviderType type, TwoFactorProvider provider) - : base("twoFactorProvider") + : base(ResponseObj) { if(provider == null) { @@ -19,7 +21,7 @@ namespace Bit.Core.Models.Api } public TwoFactorProviderResponseModel(TwoFactorProviderType type, User user) - : base("twoFactorProvider") + : base(ResponseObj) { if(user == null) { @@ -31,6 +33,19 @@ namespace Bit.Core.Models.Api Type = type; } + public TwoFactorProviderResponseModel(TwoFactorProviderType type, Organization organization) + : base(ResponseObj) + { + if(organization == null) + { + throw new ArgumentNullException(nameof(organization)); + } + + var provider = organization.GetTwoFactorProvider(type); + Enabled = provider?.Enabled ?? false; + Type = type; + } + public bool Enabled { get; set; } public TwoFactorProviderType Type { get; set; } } diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs index a5b985965c..181da0eb5c 100644 --- a/src/Core/Services/IOrganizationService.cs +++ b/src/Core/Services/IOrganizationService.cs @@ -26,6 +26,8 @@ namespace Bit.Core.Services Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate); Task EnableAsync(Guid organizationId); Task UpdateAsync(Organization organization, bool updateBilling = false); + Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type); + Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type); Task InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email, OrganizationUserType type, bool accessAll, string externalId, IEnumerable collections); Task> InviteUserAsync(Guid organizationId, Guid? invitingUserId, IEnumerable emails, diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 07cd024561..e994268c0a 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -832,6 +832,47 @@ namespace Bit.Core.Services } } + public async Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type) + { + if(!type.ToString().StartsWith("Organization")) + { + throw new ArgumentException("Not an organization provider type."); + } + + if(!organization.Use2fa) + { + throw new BadRequestException("Organization cannot use 2FA."); + } + + var providers = organization.GetTwoFactorProviders(); + if(!providers?.ContainsKey(type) ?? true) + { + return; + } + + providers[type].Enabled = true; + organization.SetTwoFactorProviders(providers); + await UpdateAsync(organization); + } + + public async Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type) + { + if(!type.ToString().StartsWith("Organization")) + { + throw new ArgumentException("Not an organization provider type."); + } + + var providers = organization.GetTwoFactorProviders(); + if(!providers?.ContainsKey(type) ?? true) + { + return; + } + + providers.Remove(type); + organization.SetTwoFactorProviders(providers); + await UpdateAsync(organization); + } + public async Task InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email, OrganizationUserType type, bool accessAll, string externalId, IEnumerable collections) {