mirror of
https://github.com/bitwarden/server.git
synced 2025-04-06 21:48:12 -05:00
organization 2fa apis
This commit is contained in:
parent
a790c37fcc
commit
0d4ea5ce5b
@ -10,6 +10,7 @@ using Bit.Core.Models.Table;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
|
||||||
namespace Bit.Api.Controllers
|
namespace Bit.Api.Controllers
|
||||||
{
|
{
|
||||||
@ -18,17 +19,26 @@ namespace Bit.Api.Controllers
|
|||||||
public class TwoFactorController : Controller
|
public class TwoFactorController : Controller
|
||||||
{
|
{
|
||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
|
private readonly IOrganizationService _organizationService;
|
||||||
private readonly GlobalSettings _globalSettings;
|
private readonly GlobalSettings _globalSettings;
|
||||||
private readonly UserManager<User> _userManager;
|
private readonly UserManager<User> _userManager;
|
||||||
|
private readonly CurrentContext _currentContext;
|
||||||
|
|
||||||
public TwoFactorController(
|
public TwoFactorController(
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
|
IOrganizationRepository organizationRepository,
|
||||||
|
IOrganizationService organizationService,
|
||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
UserManager<User> userManager)
|
UserManager<User> userManager,
|
||||||
|
CurrentContext currentContext)
|
||||||
{
|
{
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
|
_organizationRepository = organizationRepository;
|
||||||
|
_organizationService = organizationService;
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
|
_currentContext = currentContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("")]
|
[HttpGet("")]
|
||||||
@ -40,7 +50,28 @@ namespace Bit.Api.Controllers
|
|||||||
throw new UnauthorizedAccessException();
|
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<TwoFactorProviderResponseModel>(providers);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("~/organizations/{id}/two-factor")]
|
||||||
|
public async Task<ListResponseModel<TwoFactorProviderResponseModel>> 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<TwoFactorProviderResponseModel>(providers);
|
return new ListResponseModel<TwoFactorProviderResponseModel>(providers);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,6 +147,54 @@ namespace Bit.Api.Controllers
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("~/organizations/{id}/two-factor/get-duo")]
|
||||||
|
public async Task<TwoFactorDuoResponseModel> 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<TwoFactorDuoResponseModel> 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")]
|
[HttpPost("get-u2f")]
|
||||||
public async Task<TwoFactorU2fResponseModel> GetU2f([FromBody]TwoFactorRequestModel model)
|
public async Task<TwoFactorU2fResponseModel> GetU2f([FromBody]TwoFactorRequestModel model)
|
||||||
{
|
{
|
||||||
@ -205,6 +284,30 @@ namespace Bit.Api.Controllers
|
|||||||
var response = new TwoFactorProviderResponseModel(model.Type.Value, user);
|
var response = new TwoFactorProviderResponseModel(model.Type.Value, user);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPut("~/organizations/{id}/two-factor/disable")]
|
||||||
|
[HttpPost("~/organizations/{id}/two-factor/disable")]
|
||||||
|
public async Task<TwoFactorProviderResponseModel> 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")]
|
[HttpPost("get-recover")]
|
||||||
public async Task<TwoFactorRecoverResponseModel> GetRecover([FromBody]TwoFactorRequestModel model)
|
public async Task<TwoFactorRecoverResponseModel> GetRecover([FromBody]TwoFactorRequestModel model)
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
Duo = 2,
|
Duo = 2,
|
||||||
YubiKey = 3,
|
YubiKey = 3,
|
||||||
U2f = 4,
|
U2f = 4,
|
||||||
Remember = 5
|
Remember = 5,
|
||||||
|
OrganizationDuo = 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Api
|
namespace Bit.Core.Models.Api
|
||||||
@ -76,6 +75,32 @@ namespace Bit.Core.Models.Api
|
|||||||
return extistingUser;
|
return extistingUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Organization ToOrganization(Organization extistingOrg)
|
||||||
|
{
|
||||||
|
var providers = extistingOrg.GetTwoFactorProviders();
|
||||||
|
if(providers == null)
|
||||||
|
{
|
||||||
|
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||||
|
}
|
||||||
|
else if(providers.ContainsKey(TwoFactorProviderType.OrganizationDuo))
|
||||||
|
{
|
||||||
|
providers.Remove(TwoFactorProviderType.OrganizationDuo);
|
||||||
|
}
|
||||||
|
|
||||||
|
providers.Add(TwoFactorProviderType.OrganizationDuo, new TwoFactorProvider
|
||||||
|
{
|
||||||
|
MetaData = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["SKey"] = SecretKey,
|
||||||
|
["IKey"] = IntegrationKey,
|
||||||
|
["Host"] = Host
|
||||||
|
},
|
||||||
|
Enabled = true
|
||||||
|
});
|
||||||
|
extistingOrg.SetTwoFactorProviders(providers);
|
||||||
|
return extistingOrg;
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||||
{
|
{
|
||||||
if(!Host.StartsWith("api-") || !Host.EndsWith(".duosecurity.com") || Host.Count(s => s == '.') != 2)
|
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
|
public class TwoFactorProviderRequestModel : TwoFactorRequestModel
|
||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
public Enums.TwoFactorProviderType? Type { get; set; }
|
public TwoFactorProviderType? Type { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TwoFactorRequestModel
|
public class TwoFactorRequestModel
|
||||||
|
@ -6,8 +6,10 @@ namespace Bit.Core.Models.Api
|
|||||||
{
|
{
|
||||||
public class TwoFactorDuoResponseModel : ResponseModel
|
public class TwoFactorDuoResponseModel : ResponseModel
|
||||||
{
|
{
|
||||||
|
private const string ResponseObj = "twoFactorDuo";
|
||||||
|
|
||||||
public TwoFactorDuoResponseModel(User user)
|
public TwoFactorDuoResponseModel(User user)
|
||||||
: base("twoFactorDuo")
|
: base(ResponseObj)
|
||||||
{
|
{
|
||||||
if(user == null)
|
if(user == null)
|
||||||
{
|
{
|
||||||
@ -15,6 +17,28 @@ namespace Bit.Core.Models.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo);
|
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)
|
if(provider?.MetaData != null && provider.MetaData.Count > 0)
|
||||||
{
|
{
|
||||||
Enabled = provider.Enabled;
|
Enabled = provider.Enabled;
|
||||||
@ -37,10 +61,5 @@ namespace Bit.Core.Models.Api
|
|||||||
Enabled = false;
|
Enabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Enabled { get; set; }
|
|
||||||
public string Host { get; set; }
|
|
||||||
public string SecretKey { get; set; }
|
|
||||||
public string IntegrationKey { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,10 @@ namespace Bit.Core.Models.Api
|
|||||||
{
|
{
|
||||||
public class TwoFactorProviderResponseModel : ResponseModel
|
public class TwoFactorProviderResponseModel : ResponseModel
|
||||||
{
|
{
|
||||||
|
private const string ResponseObj = "twoFactorProvider";
|
||||||
|
|
||||||
public TwoFactorProviderResponseModel(TwoFactorProviderType type, TwoFactorProvider provider)
|
public TwoFactorProviderResponseModel(TwoFactorProviderType type, TwoFactorProvider provider)
|
||||||
: base("twoFactorProvider")
|
: base(ResponseObj)
|
||||||
{
|
{
|
||||||
if(provider == null)
|
if(provider == null)
|
||||||
{
|
{
|
||||||
@ -19,7 +21,7 @@ namespace Bit.Core.Models.Api
|
|||||||
}
|
}
|
||||||
|
|
||||||
public TwoFactorProviderResponseModel(TwoFactorProviderType type, User user)
|
public TwoFactorProviderResponseModel(TwoFactorProviderType type, User user)
|
||||||
: base("twoFactorProvider")
|
: base(ResponseObj)
|
||||||
{
|
{
|
||||||
if(user == null)
|
if(user == null)
|
||||||
{
|
{
|
||||||
@ -31,6 +33,19 @@ namespace Bit.Core.Models.Api
|
|||||||
Type = type;
|
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 bool Enabled { get; set; }
|
||||||
public TwoFactorProviderType Type { get; set; }
|
public TwoFactorProviderType Type { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,8 @@ namespace Bit.Core.Services
|
|||||||
Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate);
|
Task UpdateExpirationDateAsync(Guid organizationId, DateTime? expirationDate);
|
||||||
Task EnableAsync(Guid organizationId);
|
Task EnableAsync(Guid organizationId);
|
||||||
Task UpdateAsync(Organization organization, bool updateBilling = false);
|
Task UpdateAsync(Organization organization, bool updateBilling = false);
|
||||||
|
Task UpdateTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
|
||||||
|
Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
|
||||||
Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
|
Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
|
||||||
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections);
|
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections);
|
||||||
Task<List<OrganizationUser>> InviteUserAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<string> emails,
|
Task<List<OrganizationUser>> InviteUserAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<string> emails,
|
||||||
|
@ -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<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
|
public async Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
|
||||||
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections)
|
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections)
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user