mirror of
https://github.com/bitwarden/server.git
synced 2024-12-22 16:57:36 +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:
parent
dfe5c571b9
commit
50cf16a3fb
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user