1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 21:18:13 -05:00

Enforce 2fa policy (#654)

This commit is contained in:
Kyle Spearrin 2020-02-19 14:56:16 -05:00 committed by GitHub
parent 6b6c2d862d
commit 81424a8526
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 100 additions and 15 deletions

View File

@ -120,7 +120,7 @@ namespace Bit.Api.Controllers
throw new UnauthorizedAccessException();
}
var result = await _organizationService.AcceptUserAsync(new Guid(id), user, model.Token);
var result = await _organizationService.AcceptUserAsync(new Guid(id), user, model.Token, _userService);
}
[HttpPost("{id}/confirm")]

View File

@ -18,15 +18,21 @@ namespace Bit.Api.Controllers
{
private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly IOrganizationService _organizationService;
private readonly IUserService _userService;
private readonly CurrentContext _currentContext;
public PoliciesController(
IPolicyRepository policyRepository,
IPolicyService policyService,
IOrganizationService organizationService,
IUserService userService,
CurrentContext currentContext)
{
_policyRepository = policyRepository;
_policyService = policyService;
_organizationService = organizationService;
_userService = userService;
_currentContext = currentContext;
}
@ -79,7 +85,8 @@ namespace Bit.Api.Controllers
policy = model.ToPolicy(policy);
}
await _policyService.SaveAsync(policy);
var userId = _userService.GetProperUserId(User);
await _policyService.SaveAsync(policy, _userService, _organizationService, userId);
return new PolicyResponseModel(policy);
}
}

View File

@ -318,7 +318,7 @@ namespace Bit.Api.Controllers
public async Task<TwoFactorProviderResponseModel> PutDisable([FromBody]TwoFactorProviderRequestModel model)
{
var user = await CheckAsync(model.MasterPasswordHash, false);
await _userService.DisableTwoFactorProviderAsync(user, model.Type.Value);
await _userService.DisableTwoFactorProviderAsync(user, model.Type.Value, _organizationService);
var response = new TwoFactorProviderResponseModel(model.Type.Value, user);
return response;
}

View File

@ -18,15 +18,21 @@ namespace Bit.Api.Public.Controllers
{
private readonly IPolicyRepository _policyRepository;
private readonly IPolicyService _policyService;
private readonly IUserService _userService;
private readonly IOrganizationService _organizationService;
private readonly CurrentContext _currentContext;
public PoliciesController(
IPolicyRepository policyRepository,
IPolicyService policyService,
IUserService userService,
IOrganizationService organizationService,
CurrentContext currentContext)
{
_policyRepository = policyRepository;
_policyService = policyService;
_userService = userService;
_organizationService = organizationService;
_currentContext = currentContext;
}
@ -93,7 +99,7 @@ namespace Bit.Api.Public.Controllers
{
policy = model.ToPolicy(policy);
}
await _policyService.SaveAsync(policy);
await _policyService.SaveAsync(policy, _userService, _organizationService, null);
var response = new PolicyResponseModel(policy);
return new JsonResult(response);
}

View File

@ -28,7 +28,7 @@ namespace Bit.Core.Models.Api
Collections = collections?.Select(
c => new CollectionDetailsResponseModel(c)) ?? new List<CollectionDetailsResponseModel>();
Domains = excludeDomains ? null : new DomainsResponseModel(user, false);
Policies = policies.Select(p => new PolicyResponseModel(p));
Policies = policies?.Select(p => new PolicyResponseModel(p));
}
public ProfileResponseModel Profile { get; set; }

View File

@ -31,11 +31,14 @@ namespace Bit.Core.Services
Task DisableTwoFactorProviderAsync(Organization organization, TwoFactorProviderType type);
Task<OrganizationUser> InviteUserAsync(Guid organizationId, Guid? invitingUserId, string email,
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections);
Task<List<OrganizationUser>> InviteUserAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<string> emails,
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<SelectionReadOnly> collections);
Task<List<OrganizationUser>> InviteUserAsync(Guid organizationId, Guid? invitingUserId,
IEnumerable<string> emails, OrganizationUserType type, bool accessAll, string externalId,
IEnumerable<SelectionReadOnly> collections);
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId);
Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token);
Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, Guid confirmingUserId);
Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
IUserService userService);
Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
Guid confirmingUserId);
Task SaveUserAsync(OrganizationUser user, Guid? savingUserId, IEnumerable<SelectionReadOnly> collections);
Task DeleteUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId);
Task DeleteUserAsync(Guid organizationId, Guid userId);

View File

@ -1,10 +1,12 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
using Bit.Core.Models.Table;
namespace Bit.Core.Services
{
public interface IPolicyService
{
Task SaveAsync(Policy policy);
Task SaveAsync(Policy policy, IUserService userService, IOrganizationService organizationService,
Guid? savingUserId);
}
}

View File

@ -37,7 +37,8 @@ namespace Bit.Core.Services
IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
Task<IdentityResult> RefreshSecurityStampAsync(User user, string masterPasswordHash);
Task UpdateTwoFactorProviderAsync(User user, TwoFactorProviderType type);
Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type);
Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type,
IOrganizationService organizationService);
Task<bool> RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode);
Task<string> GenerateUserTokenAsync(User user, string tokenProvider, string purpose);
Task<IdentityResult> DeleteAsync(User user);

View File

@ -960,7 +960,8 @@ namespace Bit.Core.Services
await _mailService.SendOrganizationInviteEmailAsync(org.Name, orgUser, token);
}
public async Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token)
public async Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
IUserService userService)
{
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
if(orgUser == null)
@ -1005,6 +1006,16 @@ namespace Bit.Core.Services
throw new BadRequestException("Invalid token.");
}
if(!await userService.TwoFactorIsEnabledAsync(user))
{
var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgUser.OrganizationId);
if(policies.Any(p => p.Type == PolicyType.TwoFactorAuthentication && p.Enabled))
{
throw new BadRequestException("You cannot join this organization until you enable " +
"two-step login on your user account.");
}
}
orgUser.Status = OrganizationUserStatusType.Accepted;
orgUser.UserId = user.Id;
orgUser.Email = null;

View File

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Bit.Core.Exceptions;
using Bit.Core.Models.Table;
@ -25,7 +26,8 @@ namespace Bit.Core.Services
_policyRepository = policyRepository;
}
public async Task SaveAsync(Policy policy)
public async Task SaveAsync(Policy policy, IUserService userService, IOrganizationService organizationService,
Guid? savingUserId)
{
var org = await _organizationRepository.GetByIdAsync(policy.OrganizationId);
if(org == null)
@ -43,6 +45,28 @@ namespace Bit.Core.Services
{
policy.CreationDate = now;
}
else if(policy.Enabled)
{
var currentPolicy = await _policyRepository.GetByIdAsync(policy.Id);
if(!currentPolicy?.Enabled ?? true)
{
if(currentPolicy.Type == Enums.PolicyType.TwoFactorAuthentication)
{
var orgUsers = await _organizationUserRepository.GetManyDetailsByOrganizationAsync(
policy.OrganizationId);
foreach(var orgUser in orgUsers.Where(ou =>
ou.Status != Enums.OrganizationUserStatusType.Invited &&
ou.Type != Enums.OrganizationUserType.Owner))
{
if(orgUser.UserId != savingUserId && !await userService.TwoFactorIsEnabledAsync(orgUser))
{
await organizationService.DeleteUserAsync(policy.OrganizationId, orgUser.Id,
savingUserId);
}
}
}
}
}
policy.RevisionDate = DateTime.UtcNow;
await _policyRepository.UpsertAsync(policy);
await _eventService.LogPolicyEventAsync(policy, Enums.EventType.Policy_Updated);

View File

@ -43,6 +43,7 @@ namespace Bit.Core.Services
private readonly IEventService _eventService;
private readonly IApplicationCacheService _applicationCacheService;
private readonly IPaymentService _paymentService;
private readonly IPolicyRepository _policyRepository;
private readonly IDataProtector _organizationServiceDataProtector;
private readonly CurrentContext _currentContext;
private readonly GlobalSettings _globalSettings;
@ -69,6 +70,7 @@ namespace Bit.Core.Services
IApplicationCacheService applicationCacheService,
IDataProtectionProvider dataProtectionProvider,
IPaymentService paymentService,
IPolicyRepository policyRepository,
CurrentContext currentContext,
GlobalSettings globalSettings)
: base(
@ -97,6 +99,7 @@ namespace Bit.Core.Services
_eventService = eventService;
_applicationCacheService = applicationCacheService;
_paymentService = paymentService;
_policyRepository = policyRepository;
_organizationServiceDataProtector = dataProtectionProvider.CreateProtector(
"OrganizationServiceDataProtector");
_currentContext = currentContext;
@ -638,7 +641,8 @@ namespace Bit.Core.Services
await _eventService.LogUserEventAsync(user.Id, EventType.User_Updated2fa);
}
public async Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type)
public async Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type,
IOrganizationService organizationService)
{
var providers = user.GetTwoFactorProviders();
if(!providers?.ContainsKey(type) ?? true)
@ -650,6 +654,25 @@ namespace Bit.Core.Services
user.SetTwoFactorProviders(providers);
await SaveUserAsync(user);
await _eventService.LogUserEventAsync(user.Id, EventType.User_Disabled2fa);
if(!await TwoFactorIsEnabledAsync(user))
{
var policies = await _policyRepository.GetManyByUserIdAsync(user.Id);
var twoFactorPolicies = policies.Where(p => p.Type == PolicyType.TwoFactorAuthentication && p.Enabled);
if(twoFactorPolicies.Any())
{
var userOrgs = await _organizationUserRepository.GetManyByUserAsync(user.Id);
var ownerOrgs = userOrgs.Where(o => o.Type == OrganizationUserType.Owner)
.Select(o => o.Id).ToHashSet();
foreach(var policy in twoFactorPolicies)
{
if(!ownerOrgs.Contains(policy.OrganizationId))
{
await organizationService.DeleteUserAsync(policy.OrganizationId, user.Id);
}
}
}
}
}
public async Task<bool> RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode)

View File

@ -10,6 +10,10 @@ BEGIN
[dbo].[PolicyView] P
INNER JOIN
[dbo].[OrganizationUser] OU ON P.[OrganizationId] = OU.[OrganizationId]
INNER JOIN
[dbo].[Organization] O ON OU.[OrganizationId] = O.[Id]
WHERE
OU.[UserId] = @UserId
AND OU.[Status] = 2 -- 2 = Confirmed
AND O.[Enabled] = 1
END

View File

@ -576,7 +576,11 @@ BEGIN
[dbo].[PolicyView] P
INNER JOIN
[dbo].[OrganizationUser] OU ON P.[OrganizationId] = OU.[OrganizationId]
INNER JOIN
[dbo].[Organization] O ON OU.[OrganizationId] = O.[Id]
WHERE
OU.[UserId] = @UserId
AND OU.[Status] = 2 -- 2 = Confirmed
AND O.[Enabled] = 1
END
GO