diff --git a/bitwarden_license/src/Sso/Controllers/AccountController.cs b/bitwarden_license/src/Sso/Controllers/AccountController.cs index 1d184b451..3ab0d84ab 100644 --- a/bitwarden_license/src/Sso/Controllers/AccountController.cs +++ b/bitwarden_license/src/Sso/Controllers/AccountController.cs @@ -465,7 +465,7 @@ namespace Bit.Sso.Controllers OrganizationId = orgId.Value, UserId = user.Id, Type = OrganizationUserType.User, - Status = OrganizationUserStatusType.Accepted + Status = OrganizationUserStatusType.Invited }; await _organizationUserRepository.CreateAsync(orgUser); } diff --git a/src/Core/Models/Api/Request/Accounts/SetPasswordRequestModel.cs b/src/Core/Models/Api/Request/Accounts/SetPasswordRequestModel.cs index 98c77ebe9..75cdcb277 100644 --- a/src/Core/Models/Api/Request/Accounts/SetPasswordRequestModel.cs +++ b/src/Core/Models/Api/Request/Accounts/SetPasswordRequestModel.cs @@ -19,6 +19,7 @@ namespace Bit.Core.Models.Api.Request.Accounts public KdfType Kdf { get; set; } [Required] public int KdfIterations { get; set; } + public string OrgIdentifier { get; set; } public User ToUser(User existingUser) { diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs index 5aef10fb1..2eb67e756 100644 --- a/src/Core/Services/IOrganizationService.cs +++ b/src/Core/Services/IOrganizationService.cs @@ -38,6 +38,7 @@ namespace Bit.Core.Services Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId); Task AcceptUserAsync(Guid organizationUserId, User user, string token, IUserService userService); + Task AcceptUserAsync(string orgIdentifier, User user, IUserService userService); Task ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, Guid confirmingUserId, IUserService userService); Task SaveUserAsync(OrganizationUser user, Guid? savingUserId, IEnumerable collections); diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 195c3cf26..78fafe688 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -32,7 +32,7 @@ namespace Bit.Core.Services Task ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, string key); Task ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, string key); - Task SetPasswordAsync(User user, string newMasterPassword, string key); + Task SetPasswordAsync(User user, string newMasterPassword, string key, string orgIdentifier = null); Task ChangeKdfAsync(User user, string masterPassword, string newMasterPassword, string key, KdfType kdf, int kdfIterations); Task UpdateKeyAsync(User user, string masterPassword, string key, string privateKey, diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index badb0df2a..42e0da22b 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -1074,9 +1074,9 @@ namespace Bit.Core.Services throw new BadRequestException("User invalid."); } - if (orgUser.Status != OrganizationUserStatusType.Invited) + if (!CoreHelpers.UserInviteTokenIsValid(_dataProtector, token, user.Email, orgUser.Id, _globalSettings)) { - throw new BadRequestException("Already accepted."); + throw new BadRequestException("Invalid token."); } if (string.IsNullOrWhiteSpace(orgUser.Email) || @@ -1085,6 +1085,42 @@ namespace Bit.Core.Services throw new BadRequestException("User email does not match invite."); } + var existingOrgUserCount = await _organizationUserRepository.GetCountByOrganizationAsync( + orgUser.OrganizationId, user.Email, true); + if (existingOrgUserCount > 0) + { + throw new BadRequestException("You are already part of this organization."); + } + + return await AcceptUserAsync(orgUser, user, userService); + } + + public async Task AcceptUserAsync(string orgIdentifier, User user, IUserService userService) + { + var org = await _organizationRepository.GetByIdentifierAsync(orgIdentifier); + if (org == null) + { + throw new BadRequestException("Organization invalid."); + } + + var usersOrgs = await _organizationUserRepository.GetManyByUserAsync(user.Id); + var orgUser = usersOrgs.FirstOrDefault(u => u.OrganizationId == org.Id); + if (orgUser == null) + { + throw new BadRequestException("User not found within organization."); + } + + return await AcceptUserAsync(orgUser, user, userService); + } + + private async Task AcceptUserAsync(OrganizationUser orgUser, User user, + IUserService userService) + { + if (orgUser.Status != OrganizationUserStatusType.Invited) + { + throw new BadRequestException("Already accepted."); + } + if (orgUser.Type == OrganizationUserType.Owner || orgUser.Type == OrganizationUserType.Admin) { var org = await GetOrgById(orgUser.OrganizationId); @@ -1099,18 +1135,6 @@ namespace Bit.Core.Services } } - var existingOrgUserCount = await _organizationUserRepository.GetCountByOrganizationAsync( - orgUser.OrganizationId, user.Email, true); - if (existingOrgUserCount > 0) - { - throw new BadRequestException("You are already part of this organization."); - } - - if (!CoreHelpers.UserInviteTokenIsValid(_dataProtector, token, user.Email, orgUser.Id, _globalSettings)) - { - throw new BadRequestException("Invalid token."); - } - if (!await userService.TwoFactorIsEnabledAsync(user)) { var policies = await _policyRepository.GetManyByOrganizationIdAsync(orgUser.OrganizationId); @@ -1124,10 +1148,10 @@ namespace Bit.Core.Services orgUser.Status = OrganizationUserStatusType.Accepted; orgUser.UserId = user.Id; orgUser.Email = null; + await _organizationUserRepository.ReplaceAsync(orgUser); // TODO: send notification emails to org admins and accepting user? - return orgUser; } diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index fb4b286d2..66e803620 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -48,6 +48,7 @@ namespace Bit.Core.Services private readonly IReferenceEventService _referenceEventService; private readonly CurrentContext _currentContext; private readonly GlobalSettings _globalSettings; + private readonly IOrganizationService _organizationService; public UserService( IUserRepository userRepository, @@ -74,7 +75,8 @@ namespace Bit.Core.Services IPolicyRepository policyRepository, IReferenceEventService referenceEventService, CurrentContext currentContext, - GlobalSettings globalSettings) + GlobalSettings globalSettings, + IOrganizationService organizationService) : base( store, optionsAccessor, @@ -107,6 +109,7 @@ namespace Bit.Core.Services _referenceEventService = referenceEventService; _currentContext = currentContext; _globalSettings = globalSettings; + _organizationService = organizationService; } public Guid? GetProperUserId(ClaimsPrincipal principal) @@ -579,7 +582,8 @@ namespace Bit.Core.Services return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch()); } - public async Task SetPasswordAsync(User user, string masterPassword, string key) + public async Task SetPasswordAsync(User user, string masterPassword, string key, + string orgIdentifier = null) { if (user == null) { @@ -603,7 +607,12 @@ namespace Bit.Core.Services await _userRepository.ReplaceAsync(user); await _eventService.LogUserEventAsync(user.Id, EventType.User_ChangedPassword); - + + if (!string.IsNullOrWhiteSpace(orgIdentifier)) + { + await _organizationService.AcceptUserAsync(orgIdentifier, user, this); + } + return IdentityResult.Success; } diff --git a/test/Core.Test/Services/UserServiceTests.cs b/test/Core.Test/Services/UserServiceTests.cs index 36cc211cb..ecb62a5d0 100644 --- a/test/Core.Test/Services/UserServiceTests.cs +++ b/test/Core.Test/Services/UserServiceTests.cs @@ -41,6 +41,7 @@ namespace Bit.Core.Test.Services private readonly IReferenceEventService _referenceEventService; private readonly CurrentContext _currentContext; private readonly GlobalSettings _globalSettings; + private readonly IOrganizationService _organizationService; public UserServiceTests() { @@ -69,6 +70,7 @@ namespace Bit.Core.Test.Services _referenceEventService = Substitute.For(); _currentContext = new CurrentContext(); _globalSettings = new GlobalSettings(); + _organizationService = Substitute.For(); _sut = new UserService( _userRepository, @@ -95,7 +97,8 @@ namespace Bit.Core.Test.Services _policyRepository, _referenceEventService, _currentContext, - _globalSettings + _globalSettings, + _organizationService ); }