diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index 08d62d7c08..ad35306cb8 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -45,7 +45,8 @@ namespace Bit.Api.Controllers [AllowAnonymous] public async Task PostRegister([FromBody]RegisterRequestModel model) { - var result = await _userService.RegisterUserAsync(model.ToUser(), model.MasterPasswordHash); + var result = await _userService.RegisterUserAsync(model.ToUser(), model.MasterPasswordHash, + model.Token, model.OrganizationUserId); if(result.Succeeded) { return; diff --git a/src/Core/Models/Api/Request/Accounts/RegisterRequestModel.cs b/src/Core/Models/Api/Request/Accounts/RegisterRequestModel.cs index 859a4e7e1d..a27d9ef0ea 100644 --- a/src/Core/Models/Api/Request/Accounts/RegisterRequestModel.cs +++ b/src/Core/Models/Api/Request/Accounts/RegisterRequestModel.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; using Bit.Core.Models.Table; namespace Bit.Core.Models.Api @@ -18,6 +19,8 @@ namespace Bit.Core.Models.Api public string MasterPasswordHint { get; set; } public string Key { get; set; } public KeysRequestModel Keys { get; set; } + public string Token { get; set; } + public Guid? OrganizationUserId { get; set; } public User ToUser() { diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 590e851865..88e39b259d 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -18,7 +18,7 @@ namespace Bit.Core.Services Task GetUserByPrincipalAsync(ClaimsPrincipal principal); Task GetAccountRevisionDateByIdAsync(Guid userId); Task SaveUserAsync(User user, bool push = false); - Task RegisterUserAsync(User user, string masterPassword); + Task RegisterUserAsync(User user, string masterPassword, string token, Guid? orgUserId); Task SendMasterPasswordHintAsync(string email); Task SendTwoFactorEmailAsync(User user); Task VerifyTwoFactorEmailAsync(User user, string token); diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index e994268c0a..359d0a1426 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -1012,26 +1012,7 @@ namespace Bit.Core.Services throw new BadRequestException("You are already part of this organization."); } - var tokenValidationFailed = true; - try - { - var unprotectedData = _dataProtector.Unprotect(token); - var dataParts = unprotectedData.Split(' '); - if(dataParts.Length == 4 && - dataParts[0] == "OrganizationUserInvite" && - new Guid(dataParts[1]) == orgUser.Id && - dataParts[2].Equals(user.Email, StringComparison.InvariantCultureIgnoreCase)) - { - var creationTime = CoreHelpers.FromEpocMilliseconds(Convert.ToInt64(dataParts[3])); - tokenValidationFailed = creationTime.AddDays(5) < DateTime.UtcNow; - } - } - catch - { - tokenValidationFailed = true; - } - - if(tokenValidationFailed) + if(!CoreHelpers.UserInviteTokenIsValid(_dataProtector, token, user.Email, orgUser.Id)) { throw new BadRequestException("Invalid token."); } diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index aa5e8666c5..eefadac2a3 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -18,6 +18,7 @@ using Bit.Core.Exceptions; using Bit.Core.Utilities; using System.IO; using Newtonsoft.Json; +using Microsoft.AspNetCore.DataProtection; namespace Bit.Core.Services { @@ -38,6 +39,7 @@ namespace Bit.Core.Services private readonly IEnumerable> _passwordValidators; private readonly ILicensingService _licenseService; private readonly IEventService _eventService; + private readonly IDataProtector _organizationServiceDataProtector; private readonly CurrentContext _currentContext; private readonly GlobalSettings _globalSettings; @@ -59,6 +61,7 @@ namespace Bit.Core.Services ILogger> logger, ILicensingService licenseService, IEventService eventService, + IDataProtectionProvider dataProtectionProvider, CurrentContext currentContext, GlobalSettings globalSettings) : base( @@ -84,6 +87,8 @@ namespace Bit.Core.Services _passwordValidators = passwordValidators; _licenseService = licenseService; _eventService = eventService; + _organizationServiceDataProtector = dataProtectionProvider.CreateProtector( + "OrganizationServiceDataProtector"); _currentContext = currentContext; _globalSettings = globalSettings; } @@ -204,11 +209,19 @@ namespace Bit.Core.Services await _mailService.SendVerifyDeleteEmailAsync(user.Email, user.Id, token); } - public async Task RegisterUserAsync(User user, string masterPassword) + public async Task RegisterUserAsync(User user, string masterPassword, + string token, Guid? orgUserId) { - if(_globalSettings.DisableUserRegistration) + var tokenValid = false; + if(!string.IsNullOrWhiteSpace(token) && orgUserId.HasValue) { - throw new BadRequestException("Registration has been disabled by the system administrator."); + tokenValid = CoreHelpers.UserInviteTokenIsValid(_organizationServiceDataProtector, token, + user.Email, orgUserId.Value); + } + + if(_globalSettings.DisableUserRegistration && !tokenValid) + { + throw new BadRequestException("Open registration has been disabled by the system administrator."); } var result = await base.CreateAsync(user, masterPassword); diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index e4c29d47d8..b9abb0be53 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -13,6 +13,7 @@ using System.Text.RegularExpressions; using Dapper; using System.Globalization; using System.Web; +using Microsoft.AspNetCore.DataProtection; namespace Bit.Core.Utilities { @@ -452,5 +453,29 @@ namespace Bit.Core.Utilities } return new Uri(string.Format("{0}?{1}", baseUri, queryCollection), uriKind); } + + public static bool UserInviteTokenIsValid(IDataProtector protector, string token, + string userEmail, Guid orgUserId) + { + var invalid = true; + try + { + var unprotectedData = protector.Unprotect(token); + var dataParts = unprotectedData.Split(' '); + if(dataParts.Length == 4 && dataParts[0] == "OrganizationUserInvite" && + new Guid(dataParts[1]) == orgUserId && + dataParts[2].Equals(userEmail, StringComparison.InvariantCultureIgnoreCase)) + { + var creationTime = FromEpocMilliseconds(Convert.ToInt64(dataParts[3])); + invalid = creationTime.AddDays(5) < DateTime.UtcNow; + } + } + catch + { + invalid = true; + } + + return !invalid; + } } }