mirror of
https://github.com/bitwarden/server.git
synced 2024-11-25 12:45:18 +01:00
[PM-1033] refactor(wip): make AcceptUserCommand
This commit is contained in:
parent
43df689c7f
commit
dc1319e7fa
@ -12,6 +12,7 @@ using Bit.Core.OrganizationFeatures.OrganizationUsers;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@ -30,6 +31,7 @@ public class OrganizationUsersController : Controller
|
||||
private readonly IPolicyRepository _policyRepository;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IUpdateUserResetPasswordEnrollmentCommand _updateUserResetPasswordEnrollmentCommand;
|
||||
private readonly IAcceptUserCommand _acceptUserCommand;
|
||||
|
||||
public OrganizationUsersController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
@ -40,6 +42,7 @@ public class OrganizationUsersController : Controller
|
||||
IUserService userService,
|
||||
IPolicyRepository policyRepository,
|
||||
IUpdateUserResetPasswordEnrollmentCommand updateUserResetPasswordEnrollmentCommand,
|
||||
IAcceptUserCommand acceptUserCommand,
|
||||
ICurrentContext currentContext)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
@ -50,6 +53,7 @@ public class OrganizationUsersController : Controller
|
||||
_userService = userService;
|
||||
_policyRepository = policyRepository;
|
||||
_updateUserResetPasswordEnrollmentCommand = updateUserResetPasswordEnrollmentCommand;
|
||||
_acceptUserCommand = acceptUserCommand;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
@ -192,7 +196,7 @@ public class OrganizationUsersController : Controller
|
||||
}
|
||||
|
||||
await _organizationService.InitPendingOrganization(user.Id, orgId, model.Keys.PublicKey, model.Keys.EncryptedPrivateKey, model.CollectionName);
|
||||
await _organizationService.AcceptUserAsync(organizationUserId, user, model.Token, _userService);
|
||||
await _acceptUserCommand.AcceptAsync(organizationUserId, user, model.Token);
|
||||
await _organizationService.ConfirmUserAsync(orgId, organizationUserId, model.Key, user.Id, _userService);
|
||||
}
|
||||
|
||||
@ -214,7 +218,7 @@ public class OrganizationUsersController : Controller
|
||||
throw new BadRequestException(string.Empty, "Master Password reset is required, but not provided.");
|
||||
}
|
||||
|
||||
await _organizationService.AcceptUserAsync(organizationUserId, user, model.Token, _userService);
|
||||
await _acceptUserCommand.AcceptAsync(organizationUserId, user, model.Token);
|
||||
|
||||
if (useMasterPasswordPolicy)
|
||||
{
|
||||
@ -310,7 +314,7 @@ public class OrganizationUsersController : Controller
|
||||
}
|
||||
|
||||
[HttpPut("{userId}/reset-password-enrollment")]
|
||||
public async Task PutResetPasswordEnrollment(string orgId, string userId, [FromBody] OrganizationUserResetPasswordEnrollmentRequestModel model)
|
||||
public async Task PutResetPasswordEnrollment(Guid organizationId, Guid userId, [FromBody] OrganizationUserResetPasswordEnrollmentRequestModel model)
|
||||
{
|
||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||
if (user == null)
|
||||
@ -320,13 +324,13 @@ public class OrganizationUsersController : Controller
|
||||
|
||||
var callingUserId = user.Id;
|
||||
await _updateUserResetPasswordEnrollmentCommand.UpdateAsync(
|
||||
new Guid(orgId), new Guid(userId), model.ResetPasswordKey, callingUserId);
|
||||
organizationId, userId, model.ResetPasswordKey, callingUserId);
|
||||
|
||||
//if (orgUser.Status == OrganizationUserStatusType.Invited)
|
||||
//{
|
||||
// var user = await _userRepository.GetByIdAsync(userId);
|
||||
// await _organizationService.AcceptUserAsync(orgUser, user, _userService);
|
||||
//}
|
||||
var orgUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId);
|
||||
if (orgUser.Status == OrganizationUserStatusType.Invited)
|
||||
{
|
||||
await _acceptUserCommand.AcceptAsync(organizationId, user);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id}/reset-password")]
|
||||
|
@ -0,0 +1,187 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationUsers;
|
||||
|
||||
public class AcceptUserCommand : IAcceptUserCommand
|
||||
{
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IDataProtector _dataProtector;
|
||||
private readonly IMailService _mailService;
|
||||
private readonly IPolicyService _policyService;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public AcceptUserCommand(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IDataProtector dataProtector,
|
||||
IMailService mailService,
|
||||
IPolicyService policyService,
|
||||
IGlobalSettings globalSettings,
|
||||
IUserService userService)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_dataProtector = dataProtector;
|
||||
_mailService = mailService;
|
||||
_policyService = policyService;
|
||||
_globalSettings = globalSettings;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public async Task<OrganizationUser> AcceptAsync(Guid organizationUserId, User user, string token)
|
||||
{
|
||||
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
||||
if (orgUser == null)
|
||||
{
|
||||
throw new BadRequestException("User invalid.");
|
||||
}
|
||||
|
||||
if (!CoreHelpers.UserInviteTokenIsValid(_dataProtector, token, user.Email, orgUser.Id, _globalSettings))
|
||||
{
|
||||
throw new BadRequestException("Invalid token.");
|
||||
}
|
||||
|
||||
var existingOrgUserCount = await _organizationUserRepository.GetCountByOrganizationAsync(
|
||||
orgUser.OrganizationId, user.Email, true);
|
||||
if (existingOrgUserCount > 0)
|
||||
{
|
||||
if (orgUser.Status == OrganizationUserStatusType.Accepted)
|
||||
{
|
||||
throw new BadRequestException("Invitation already accepted. You will receive an email when your organization membership is confirmed.");
|
||||
}
|
||||
throw new BadRequestException("You are already part of this organization.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(orgUser.Email) ||
|
||||
!orgUser.Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
throw new BadRequestException("User email does not match invite.");
|
||||
}
|
||||
|
||||
return await AcceptAsync(orgUser, user);
|
||||
}
|
||||
|
||||
public async Task<OrganizationUser> AcceptAsync(string orgIdentifier, User user)
|
||||
{
|
||||
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 AcceptAsync(orgUser, user);
|
||||
}
|
||||
|
||||
public async Task<OrganizationUser> AcceptAsync(Guid organizationId, User user)
|
||||
{
|
||||
var org = await _organizationRepository.GetByIdAsync(organizationId);
|
||||
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 AcceptAsync(orgUser, user);
|
||||
}
|
||||
|
||||
private async Task<OrganizationUser> AcceptAsync(OrganizationUser orgUser, User user)
|
||||
{
|
||||
if (orgUser.Status == OrganizationUserStatusType.Revoked)
|
||||
{
|
||||
throw new BadRequestException("Your organization access has been revoked.");
|
||||
}
|
||||
|
||||
if (orgUser.Status != OrganizationUserStatusType.Invited)
|
||||
{
|
||||
throw new BadRequestException("Already accepted.");
|
||||
}
|
||||
|
||||
if (orgUser.Type == OrganizationUserType.Owner || orgUser.Type == OrganizationUserType.Admin)
|
||||
{
|
||||
var org = await _organizationRepository.GetByIdAsync(orgUser.OrganizationId);
|
||||
if (org.PlanType == PlanType.Free)
|
||||
{
|
||||
var adminCount = await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(
|
||||
user.Id);
|
||||
if (adminCount > 0)
|
||||
{
|
||||
throw new BadRequestException("You can only be an admin of one free organization.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce Single Organization Policy of organization user is trying to join
|
||||
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(user.Id);
|
||||
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
|
||||
var invitedSingleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
|
||||
PolicyType.SingleOrg, OrganizationUserStatusType.Invited);
|
||||
|
||||
if (hasOtherOrgs && invitedSingleOrgPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
||||
{
|
||||
throw new BadRequestException("You may not join this organization until you leave or remove " +
|
||||
"all other organizations.");
|
||||
}
|
||||
|
||||
// Enforce Single Organization Policy of other organizations user is a member of
|
||||
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(user.Id,
|
||||
PolicyType.SingleOrg);
|
||||
if (anySingleOrgPolicies)
|
||||
{
|
||||
throw new BadRequestException("You cannot join this organization because you are a member of " +
|
||||
"another organization which forbids it");
|
||||
}
|
||||
|
||||
// Enforce Two Factor Authentication Policy of organization user is trying to join
|
||||
if (!await _userService.TwoFactorIsEnabledAsync(user))
|
||||
{
|
||||
var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
|
||||
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
|
||||
if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
||||
{
|
||||
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;
|
||||
|
||||
await _organizationUserRepository.ReplaceAsync(orgUser);
|
||||
|
||||
var admins = await _organizationUserRepository.GetManyByMinimumRoleAsync(orgUser.OrganizationId, OrganizationUserType.Admin);
|
||||
var adminEmails = admins.Select(a => a.Email).Distinct().ToList();
|
||||
|
||||
if (adminEmails.Count > 0)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(orgUser.OrganizationId);
|
||||
await _mailService.SendOrganizationAcceptedEmailAsync(organization, user.Email, adminEmails);
|
||||
}
|
||||
|
||||
return orgUser;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
using Bit.Core.Entities;
|
||||
|
||||
namespace Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
|
||||
public interface IAcceptUserCommand
|
||||
{
|
||||
Task<OrganizationUser> AcceptAsync(Guid organizationUserId, User user, string token);
|
||||
|
||||
Task<OrganizationUser> AcceptAsync(string orgIdentifier, User user);
|
||||
|
||||
Task<OrganizationUser> AcceptAsync(Guid organizationId, User user);
|
||||
}
|
||||
|
@ -40,8 +40,6 @@ public interface IOrganizationService
|
||||
OrganizationUserType type, bool accessAll, string externalId, IEnumerable<CollectionAccessSelection> collections, IEnumerable<Guid> groups);
|
||||
Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<Guid> organizationUsersId);
|
||||
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false);
|
||||
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<List<Tuple<OrganizationUser, string>>> ConfirmUsersAsync(Guid organizationId, Dictionary<Guid, string> keys,
|
||||
|
@ -1213,154 +1213,6 @@ public class OrganizationService : IOrganizationService
|
||||
await _mailService.SendOrganizationInviteEmailAsync(organization.Name, orgUser, new ExpiringToken(token, now.AddDays(5)), organization.PlanType == PlanType.Free, initOrganization);
|
||||
}
|
||||
|
||||
public async Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
|
||||
IUserService userService)
|
||||
{
|
||||
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
||||
if (orgUser == null)
|
||||
{
|
||||
throw new BadRequestException("User invalid.");
|
||||
}
|
||||
|
||||
if (!CoreHelpers.UserInviteTokenIsValid(_dataProtector, token, user.Email, orgUser.Id, _globalSettings))
|
||||
{
|
||||
throw new BadRequestException("Invalid token.");
|
||||
}
|
||||
|
||||
var existingOrgUserCount = await _organizationUserRepository.GetCountByOrganizationAsync(
|
||||
orgUser.OrganizationId, user.Email, true);
|
||||
if (existingOrgUserCount > 0)
|
||||
{
|
||||
if (orgUser.Status == OrganizationUserStatusType.Accepted)
|
||||
{
|
||||
throw new BadRequestException("Invitation already accepted. You will receive an email when your organization membership is confirmed.");
|
||||
}
|
||||
throw new BadRequestException("You are already part of this organization.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(orgUser.Email) ||
|
||||
!orgUser.Email.Equals(user.Email, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
throw new BadRequestException("User email does not match invite.");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public async Task<OrganizationUser> AcceptUserAsync(Guid organizationId, User user, IUserService userService)
|
||||
{
|
||||
var org = await _organizationRepository.GetByIdAsync(organizationId);
|
||||
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.Revoked)
|
||||
{
|
||||
throw new BadRequestException("Your organization access has been revoked.");
|
||||
}
|
||||
|
||||
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);
|
||||
if (org.PlanType == PlanType.Free)
|
||||
{
|
||||
var adminCount = await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(
|
||||
user.Id);
|
||||
if (adminCount > 0)
|
||||
{
|
||||
throw new BadRequestException("You can only be an admin of one free organization.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce Single Organization Policy of organization user is trying to join
|
||||
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(user.Id);
|
||||
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
|
||||
var invitedSingleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
|
||||
PolicyType.SingleOrg, OrganizationUserStatusType.Invited);
|
||||
|
||||
if (hasOtherOrgs && invitedSingleOrgPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
||||
{
|
||||
throw new BadRequestException("You may not join this organization until you leave or remove " +
|
||||
"all other organizations.");
|
||||
}
|
||||
|
||||
// Enforce Single Organization Policy of other organizations user is a member of
|
||||
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(user.Id,
|
||||
PolicyType.SingleOrg);
|
||||
if (anySingleOrgPolicies)
|
||||
{
|
||||
throw new BadRequestException("You cannot join this organization because you are a member of " +
|
||||
"another organization which forbids it");
|
||||
}
|
||||
|
||||
// Enforce Two Factor Authentication Policy of organization user is trying to join
|
||||
if (!await userService.TwoFactorIsEnabledAsync(user))
|
||||
{
|
||||
var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
|
||||
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
|
||||
if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
||||
{
|
||||
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;
|
||||
|
||||
await _organizationUserRepository.ReplaceAsync(orgUser);
|
||||
|
||||
var admins = await _organizationUserRepository.GetManyByMinimumRoleAsync(orgUser.OrganizationId, OrganizationUserType.Admin);
|
||||
var adminEmails = admins.Select(a => a.Email).Distinct().ToList();
|
||||
|
||||
if (adminEmails.Count > 0)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(orgUser.OrganizationId);
|
||||
await _mailService.SendOrganizationAcceptedEmailAsync(organization, user.Email, adminEmails);
|
||||
}
|
||||
|
||||
return orgUser;
|
||||
}
|
||||
|
||||
public async Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
|
||||
Guid confirmingUserId, IUserService userService)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user