1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-21 12:05:42 +01:00

[SSO] New user provision flow (#945)

* Initial commit of accept user during set password flow

* changed new org user from accepted to invited // moved another check to token accept function

* Revised some white space // Moved business logic to UserService

* Fixed UserServiceTest

* Removed some white-space

* Removed more white-space

* Final white-space issues
This commit is contained in:
Vincent Salucci 2020-10-13 15:00:33 -05:00 committed by GitHub
parent dfe5c571b9
commit 50cf16a3fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 59 additions and 21 deletions

View File

@ -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);
}

View File

@ -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)
{

View File

@ -38,6 +38,7 @@ namespace Bit.Core.Services
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId);
Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
IUserService userService);
Task<OrganizationUser> AcceptUserAsync(string orgIdentifier, User user, IUserService userService);
Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
Guid confirmingUserId, IUserService userService);
Task SaveUserAsync(OrganizationUser user, Guid? savingUserId, IEnumerable<SelectionReadOnly> collections);

View File

@ -32,7 +32,7 @@ namespace Bit.Core.Services
Task<IdentityResult> ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword,
string token, string key);
Task<IdentityResult> ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, string key);
Task<IdentityResult> SetPasswordAsync(User user, string newMasterPassword, string key);
Task<IdentityResult> SetPasswordAsync(User user, string newMasterPassword, string key, string orgIdentifier = null);
Task<IdentityResult> ChangeKdfAsync(User user, string masterPassword, string newMasterPassword, string key,
KdfType kdf, int kdfIterations);
Task<IdentityResult> UpdateKeyAsync(User user, string masterPassword, string key, string privateKey,

View File

@ -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<OrganizationUser> 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<OrganizationUser> 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;
}

View File

@ -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<IdentityResult> SetPasswordAsync(User user, string masterPassword, string key)
public async Task<IdentityResult> 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;
}

View File

@ -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<IReferenceEventService>();
_currentContext = new CurrentContext();
_globalSettings = new GlobalSettings();
_organizationService = Substitute.For<IOrganizationService>();
_sut = new UserService(
_userRepository,
@ -95,7 +97,8 @@ namespace Bit.Core.Test.Services
_policyRepository,
_referenceEventService,
_currentContext,
_globalSettings
_globalSettings,
_organizationService
);
}