mirror of
https://github.com/bitwarden/server.git
synced 2025-07-01 08:02:49 -05:00
[AC-431] Add new organization invite process (#2737)
* [EC-435] Created _OrganizationForm partial view. Added actions for creating an Organization assigned to a provider * [EC-435] Remove logic for creating an organization * [EC-435] Created partial view _OrganizationFormScripts * [EC-435] Remove unused ReferenceEventType * [EC-435] Added TODO comment on Organization Create * [EC-435] Checking if Provider type is Reseller on creating new assigned organization * [EC-435] Setting the Organization plan type as TeamsMonthly by default when adding to a provider * [EC-435] Removing unused buttons * [EC-435] Switched hidden fields to form submit route value * [EC-435] Moved _OrganizationForm and _OrganizationFormScripts to Shared folder * [EC-435] Moved Create organization actions from OrganizationsController to ProvidersController * [AC-431] Added new ReferenceEventType OrganizationCreatedByAdmin * [AC-431] Added method IOrganizationService.CreateOrganization * [AC-431] Creating new Organization with Pending status and assigning to Provider * [AC-431] Added method to IMailService to send invitation to initialize org * [AC-431] Added methods CreatePendingOrganization and InitPendingOrganization to IOrganizationService * [AC-431] Org invite includes initOrganization parameter * [AC-431] Modified existing Accept organization user action to initialize org * [AC-431] Updated ProvidersController method name * [AC-431] Created OrganizationUserInitInvitedViewModel to link to 'accept-init-organization' url * [AC-431] Added action AcceptInit to OrganizationUsersController * [AC-431] Resend owner invite * [AC-431] dotnet format * [AC-431] Removed unused parameter 'addingUserId' from IProviderService.AddOrganization * [AC-431] Removed setting manual values for CreationDate and RevisionDate * [AC-431] Updated OrganizationService.InitPendingOrganization to throw exceptions when the Organization does not meet the required criteria * [AC-431] Modified OrganizationUserInitInvitedViewModel to inherit properties from OrganizationUserInvitedViewModel * [AC-431] Removed unecessary parameter check * [AC-431] Moved method description to IOrganizationService.InitPendingOrganization * [AC-431] Moved ApplicationCacheService.UpsertOrganizationAbilityAsync and ReferenceEventService.RaiseEventAsync to OrganizationService * [AC-431] Creating collection after creating organization * [EC-435] Fixing bug on saving Organization that would have BillingEmail as null * [AC-431] Deleted OrganizationUserInitInvitedViewModel and added parameter InitOrganization to OrganizationUserInvitedViewModel.cs * [AC-431] Checking if the user has any existing SingleOrg policies before initializing an Org * [AC-431] Remove commented code * [EC-435] Added null check to Provider * [EC-435] Moved trial buttons script logic to Edit view * [AC-431] Added EncryptedString attribute to OrganizationUserAcceptInitRequestModel.CollectionName * [AC-431] Refactored plan check condition * [AC-431] Remove duplicate _applicationCacheService.UpsertOrganizationAbilityAsync call * [AC-431] Removed IMailService.SendOrganizationInitInviteEmailAsync * [AC-431] Added parameters ClaimsPrincipal and IUserService to IOrganizationService.CreatePendingOrganization
This commit is contained in:
@ -343,7 +343,7 @@ public class ProviderService : IProviderService
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key)
|
||||
public async Task AddOrganization(Guid providerId, Guid organizationId, string key)
|
||||
{
|
||||
var po = await _providerOrganizationRepository.GetByOrganizationId(organizationId);
|
||||
if (po != null)
|
||||
|
@ -418,7 +418,7 @@ public class ProviderServiceTests
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task AddOrganization_OrganizationAlreadyBelongsToAProvider_Throws(Provider provider,
|
||||
Organization organization, ProviderOrganization po, User user, string key,
|
||||
Organization organization, ProviderOrganization po, string key,
|
||||
SutProvider<ProviderService> sutProvider)
|
||||
{
|
||||
po.OrganizationId = organization.Id;
|
||||
@ -427,12 +427,12 @@ public class ProviderServiceTests
|
||||
.Returns(po);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.AddOrganization(provider.Id, organization.Id, user.Id, key));
|
||||
() => sutProvider.Sut.AddOrganization(provider.Id, organization.Id, key));
|
||||
Assert.Equal("Organization already belongs to a provider.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task AddOrganization_Success(Provider provider, Organization organization, User user, string key,
|
||||
public async Task AddOrganization_Success(Provider provider, Organization organization, string key,
|
||||
SutProvider<ProviderService> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.EnterpriseAnnually;
|
||||
@ -442,7 +442,7 @@ public class ProviderServiceTests
|
||||
providerOrganizationRepository.GetByOrganizationId(organization.Id).ReturnsNull();
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
await sutProvider.Sut.AddOrganization(provider.Id, organization.Id, user.Id, key);
|
||||
await sutProvider.Sut.AddOrganization(provider.Id, organization.Id, key);
|
||||
|
||||
await providerOrganizationRepository.ReceivedWithAnyArgs().CreateAsync(default);
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
|
@ -17,6 +17,7 @@ namespace Bit.Admin.Controllers;
|
||||
[Authorize]
|
||||
public class OrganizationsController : Controller
|
||||
{
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly IOrganizationConnectionRepository _organizationConnectionRepository;
|
||||
@ -35,6 +36,7 @@ public class OrganizationsController : Controller
|
||||
private readonly ILogger<OrganizationsController> _logger;
|
||||
|
||||
public OrganizationsController(
|
||||
IOrganizationService organizationService,
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationUserRepository organizationUserRepository,
|
||||
IOrganizationConnectionRepository organizationConnectionRepository,
|
||||
@ -52,6 +54,7 @@ public class OrganizationsController : Controller
|
||||
IProviderRepository providerRepository,
|
||||
ILogger<OrganizationsController> logger)
|
||||
{
|
||||
_organizationService = organizationService;
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationUserRepository = organizationUserRepository;
|
||||
_organizationConnectionRepository = organizationConnectionRepository;
|
||||
@ -219,4 +222,21 @@ public class OrganizationsController : Controller
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> ResendOwnerInvite(Guid id)
|
||||
{
|
||||
var organization = await _organizationRepository.GetByIdAsync(id);
|
||||
if (organization == null)
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var organizationUsers = await _organizationUserRepository.GetManyByOrganizationAsync(id, OrganizationUserType.Owner);
|
||||
foreach (var organizationUser in organizationUsers)
|
||||
{
|
||||
await _organizationService.ResendInviteAsync(id, null, organizationUser.Id, true);
|
||||
}
|
||||
|
||||
return Json(null);
|
||||
}
|
||||
}
|
||||
|
@ -16,31 +16,40 @@ namespace Bit.Admin.Controllers;
|
||||
public class ProvidersController : Controller
|
||||
{
|
||||
private readonly IOrganizationRepository _organizationRepository;
|
||||
private readonly IOrganizationService _organizationService;
|
||||
private readonly IProviderRepository _providerRepository;
|
||||
private readonly IProviderUserRepository _providerUserRepository;
|
||||
private readonly IProviderOrganizationRepository _providerOrganizationRepository;
|
||||
private readonly GlobalSettings _globalSettings;
|
||||
private readonly IApplicationCacheService _applicationCacheService;
|
||||
private readonly IProviderService _providerService;
|
||||
private readonly IReferenceEventService _referenceEventService;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ICreateProviderCommand _createProviderCommand;
|
||||
|
||||
public ProvidersController(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationService organizationService,
|
||||
IProviderRepository providerRepository,
|
||||
IProviderUserRepository providerUserRepository,
|
||||
IProviderOrganizationRepository providerOrganizationRepository,
|
||||
IProviderService providerService,
|
||||
GlobalSettings globalSettings,
|
||||
IApplicationCacheService applicationCacheService,
|
||||
IReferenceEventService referenceEventService,
|
||||
IUserService userService,
|
||||
ICreateProviderCommand createProviderCommand)
|
||||
{
|
||||
_organizationRepository = organizationRepository;
|
||||
_organizationService = organizationService;
|
||||
_providerRepository = providerRepository;
|
||||
_providerUserRepository = providerUserRepository;
|
||||
_providerOrganizationRepository = providerOrganizationRepository;
|
||||
_providerService = providerService;
|
||||
_globalSettings = globalSettings;
|
||||
_applicationCacheService = applicationCacheService;
|
||||
_referenceEventService = referenceEventService;
|
||||
_userService = userService;
|
||||
_createProviderCommand = createProviderCommand;
|
||||
}
|
||||
|
||||
@ -211,7 +220,15 @@ public class ProvidersController : Controller
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreateOrganization(Guid providerId, OrganizationEditModel model)
|
||||
{
|
||||
// TODO : Insert logic to create the new Organization entry, create an OrganizationUser entry for the owner and send the invitation email
|
||||
var provider = await _providerRepository.GetByIdAsync(providerId);
|
||||
if (provider is not { Type: ProviderType.Reseller })
|
||||
{
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
var organization = model.CreateOrganization(provider);
|
||||
await _organizationService.CreatePendingOrganization(organization, model.Owners, User, _userService, model.SalesAssistedTrialStarted);
|
||||
await _providerService.AddOrganization(providerId, organization.Id, null);
|
||||
|
||||
return RedirectToAction("Edit", "Providers", new { id = providerId });
|
||||
}
|
||||
|
@ -135,6 +135,13 @@ public class OrganizationEditModel : OrganizationViewModel
|
||||
public DateTime? ExpirationDate { get; set; }
|
||||
public bool SalesAssistedTrialStarted { get; set; }
|
||||
|
||||
public Organization CreateOrganization(Provider provider)
|
||||
{
|
||||
BillingEmail = provider.BillingEmail;
|
||||
|
||||
return ToOrganization(new Organization());
|
||||
}
|
||||
|
||||
public Organization ToOrganization(Organization existingOrganization)
|
||||
{
|
||||
existingOrganization.Name = Name;
|
||||
|
@ -1,4 +1,7 @@
|
||||
@model ProviderViewModel
|
||||
|
||||
@await Html.PartialAsync("_ProviderScripts")
|
||||
|
||||
<h2>Provider Organizations</h2>
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
@ -41,7 +44,7 @@
|
||||
<div class="float-right">
|
||||
@if (org.Status == OrganizationStatusType.Pending)
|
||||
{
|
||||
<a asp-controller="Organizations" asp-action="ResendInvite" asp-route-id="@org.Id" class="float-right">
|
||||
<a href="#" class="float-right" onclick="return resendOwnerInvite('@org.OrganizationId', '@org.OrganizationName');">
|
||||
<i class="fa fa-envelope-o fa-lg" title="Resend Setup Invite"></i>
|
||||
</a>
|
||||
}
|
||||
|
20
src/Admin/Views/Providers/_ProviderScripts.cshtml
Normal file
20
src/Admin/Views/Providers/_ProviderScripts.cshtml
Normal file
@ -0,0 +1,20 @@
|
||||
<script>
|
||||
function resendOwnerInvite(orgId, orgName) {
|
||||
if (confirm('Resend invite to "' + orgName + '"?')) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: '@Url.Action("ResendOwnerInvite", "Organizations")' + '?id=' + orgId,
|
||||
dataType: 'json',
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success: function (response) {
|
||||
alert('Invitation has been resent!');
|
||||
},
|
||||
error: function (response) {
|
||||
alert("Error!");
|
||||
}
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
</script>
|
@ -177,6 +177,20 @@ public class OrganizationUsersController : Controller
|
||||
await _organizationService.ResendInviteAsync(orgGuidId, userId.Value, new Guid(id));
|
||||
}
|
||||
|
||||
[HttpPost("{organizationUserId}/accept-init")]
|
||||
public async Task AcceptInit(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptInitRequestModel model)
|
||||
{
|
||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
await _organizationService.InitPendingOrganization(user.Id, orgId, model.Keys.PublicKey, model.Keys.EncryptedPrivateKey, model.CollectionName);
|
||||
await _organizationService.AcceptUserAsync(organizationUserId, user, model.Token, _userService);
|
||||
await _organizationService.ConfirmUserAsync(orgId, organizationUserId, model.Key, user.Id, _userService);
|
||||
}
|
||||
|
||||
[HttpPost("{organizationUserId}/accept")]
|
||||
public async Task Accept(Guid orgId, Guid organizationUserId, [FromBody] OrganizationUserAcceptRequestModel model)
|
||||
{
|
||||
@ -188,11 +202,9 @@ public class OrganizationUsersController : Controller
|
||||
|
||||
var masterPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
|
||||
var useMasterPasswordPolicy = masterPasswordPolicy != null &&
|
||||
masterPasswordPolicy.Enabled &&
|
||||
masterPasswordPolicy.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled;
|
||||
|
||||
if (useMasterPasswordPolicy &&
|
||||
string.IsNullOrWhiteSpace(model.ResetPasswordKey))
|
||||
masterPasswordPolicy.Enabled &&
|
||||
masterPasswordPolicy.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled;
|
||||
if (useMasterPasswordPolicy && string.IsNullOrWhiteSpace(model.ResetPasswordKey))
|
||||
{
|
||||
throw new BadRequestException(string.Empty, "Master Password reset is required, but not provided.");
|
||||
}
|
||||
|
@ -54,9 +54,7 @@ public class ProviderOrganizationsController : Controller
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
|
||||
await _providerService.AddOrganization(providerId, model.OrganizationId, userId, model.Key);
|
||||
await _providerService.AddOrganization(providerId, model.OrganizationId, model.Key);
|
||||
}
|
||||
|
||||
[HttpPost("")]
|
||||
|
@ -37,6 +37,19 @@ public class OrganizationUserInviteRequestModel
|
||||
}
|
||||
}
|
||||
|
||||
public class OrganizationUserAcceptInitRequestModel
|
||||
{
|
||||
[Required]
|
||||
public string Token { get; set; }
|
||||
[Required]
|
||||
public string Key { get; set; }
|
||||
[Required]
|
||||
public OrganizationKeysRequestModel Keys { get; set; }
|
||||
[EncryptedString]
|
||||
[EncryptedStringLength(1000)]
|
||||
public string CollectionName { get; set; }
|
||||
}
|
||||
|
||||
public class OrganizationUserAcceptRequestModel
|
||||
{
|
||||
[Required]
|
||||
|
@ -39,5 +39,7 @@ public enum ReferenceEventType
|
||||
[EnumMember(Value = "collection-created")]
|
||||
CollectionCreated,
|
||||
[EnumMember(Value = "organization-edited-by-admin")]
|
||||
OrganizationEditedByAdmin
|
||||
OrganizationEditedByAdmin,
|
||||
[EnumMember(Value = "organization-created-by-admin")]
|
||||
OrganizationCreatedByAdmin
|
||||
}
|
||||
|
@ -9,12 +9,14 @@ public class OrganizationUserInvitedViewModel : BaseMailModel
|
||||
public string OrganizationNameUrlEncoded { get; set; }
|
||||
public string Token { get; set; }
|
||||
public string ExpirationDate { get; set; }
|
||||
public bool InitOrganization { get; set; }
|
||||
public string Url => string.Format("{0}/accept-organization?organizationId={1}&" +
|
||||
"organizationUserId={2}&email={3}&organizationName={4}&token={5}",
|
||||
"organizationUserId={2}&email={3}&organizationName={4}&token={5}&initOrganization={6}",
|
||||
WebVaultUrl,
|
||||
OrganizationId,
|
||||
OrganizationUserId,
|
||||
Email,
|
||||
OrganizationNameUrlEncoded,
|
||||
Token);
|
||||
Token,
|
||||
InitOrganization);
|
||||
}
|
||||
|
@ -15,8 +15,8 @@ public interface IMailService
|
||||
Task SendTwoFactorEmailAsync(string email, string token);
|
||||
Task SendNoMasterPasswordHintEmailAsync(string email);
|
||||
Task SendMasterPasswordHintEmailAsync(string email, string hint);
|
||||
Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token);
|
||||
Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites);
|
||||
Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool initOrganization = false);
|
||||
Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool initOrganization = false);
|
||||
Task SendOrganizationMaxSeatLimitReachedEmailAsync(Organization organization, int maxSeatCount, IEnumerable<string> ownerEmails);
|
||||
Task SendOrganizationAutoscaledEmailAsync(Organization organization, int initialSeatCount, IEnumerable<string> ownerEmails);
|
||||
Task SendOrganizationAcceptedEmailAsync(Organization organization, string userIdentifier, IEnumerable<string> adminEmails);
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.Entities;
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
@ -37,9 +38,8 @@ public interface IOrganizationService
|
||||
Task<OrganizationUser> InviteUserAsync(Guid organizationId, EventSystemUser systemUser, string email,
|
||||
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);
|
||||
Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
|
||||
IUserService userService);
|
||||
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);
|
||||
@ -69,5 +69,13 @@ public interface IOrganizationService
|
||||
Task RestoreUserAsync(OrganizationUser organizationUser, EventSystemUser systemUser, IUserService userService);
|
||||
Task<List<Tuple<OrganizationUser, string>>> RestoreUsersAsync(Guid organizationId,
|
||||
IEnumerable<Guid> organizationUserIds, Guid? restoringUserId, IUserService userService);
|
||||
Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted);
|
||||
/// <summary>
|
||||
/// Update an Organization entry by setting the public/private keys, set it as 'Enabled' and move the Status from 'Pending' to 'Created'.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This method must target a disabled Organization that has null keys and status as 'Pending'.
|
||||
/// </remarks>
|
||||
Task InitPendingOrganization(Guid userId, Guid organizationId, string publicKey, string privateKey, string collectionName);
|
||||
Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null);
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ public interface IProviderService
|
||||
Task<List<Tuple<ProviderUser, string>>> DeleteUsersAsync(Guid providerId, IEnumerable<Guid> providerUserIds,
|
||||
Guid deletingUserId);
|
||||
|
||||
Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key);
|
||||
Task AddOrganization(Guid providerId, Guid organizationId, string key);
|
||||
Task AddOrganizationsToReseller(Guid providerId, IEnumerable<Guid> organizationIds);
|
||||
Task<ProviderOrganization> CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup,
|
||||
string clientOwnerEmail, User user);
|
||||
|
@ -200,10 +200,10 @@ public class HandlebarsMailService : IMailService
|
||||
await _mailDeliveryService.SendEmailAsync(message);
|
||||
}
|
||||
|
||||
public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token) =>
|
||||
BulkSendOrganizationInviteEmailAsync(organizationName, new[] { (orgUser, token) });
|
||||
public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool initOrganization = false) =>
|
||||
BulkSendOrganizationInviteEmailAsync(organizationName, new[] { (orgUser, token) }, initOrganization);
|
||||
|
||||
public async Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites)
|
||||
public async Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool initOrganization = false)
|
||||
{
|
||||
MailQueueMessage CreateMessage(string email, object model)
|
||||
{
|
||||
@ -223,6 +223,7 @@ public class HandlebarsMailService : IMailService
|
||||
OrganizationNameUrlEncoded = WebUtility.UrlEncode(organizationName),
|
||||
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
|
||||
SiteName = _globalSettings.SiteName,
|
||||
InitOrganization = initOrganization
|
||||
}
|
||||
));
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@ -598,7 +599,7 @@ public class OrganizationService : IOrganizationService
|
||||
bool provider = false)
|
||||
{
|
||||
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == signup.Plan);
|
||||
if (!(plan is { LegacyYear: null }))
|
||||
if (plan is not { LegacyYear: null })
|
||||
{
|
||||
throw new BadRequestException("Invalid plan selected.");
|
||||
}
|
||||
@ -1169,14 +1170,14 @@ public class OrganizationService : IOrganizationService
|
||||
continue;
|
||||
}
|
||||
|
||||
await SendInviteAsync(orgUser, org);
|
||||
await SendInviteAsync(orgUser, org, false);
|
||||
result.Add(Tuple.Create(orgUser, ""));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId)
|
||||
public async Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false)
|
||||
{
|
||||
var orgUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
||||
if (orgUser == null || orgUser.OrganizationId != organizationId ||
|
||||
@ -1186,7 +1187,7 @@ public class OrganizationService : IOrganizationService
|
||||
}
|
||||
|
||||
var org = await GetOrgById(orgUser.OrganizationId);
|
||||
await SendInviteAsync(orgUser, org);
|
||||
await SendInviteAsync(orgUser, org, initOrganization);
|
||||
}
|
||||
|
||||
private async Task SendInvitesAsync(IEnumerable<OrganizationUser> orgUsers, Organization organization)
|
||||
@ -1198,14 +1199,14 @@ public class OrganizationService : IOrganizationService
|
||||
orgUsers.Select(o => (o, new ExpiringToken(MakeToken(o), DateTime.UtcNow.AddDays(5)))));
|
||||
}
|
||||
|
||||
private async Task SendInviteAsync(OrganizationUser orgUser, Organization organization)
|
||||
private async Task SendInviteAsync(OrganizationUser orgUser, Organization organization, bool initOrganization)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var nowMillis = CoreHelpers.ToEpocMilliseconds(now);
|
||||
var token = _dataProtector.Protect(
|
||||
$"OrganizationUserInvite {orgUser.Id} {orgUser.Email} {nowMillis}");
|
||||
|
||||
await _mailService.SendOrganizationInviteEmailAsync(organization.Name, orgUser, new ExpiringToken(token, now.AddDays(5)));
|
||||
await _mailService.SendOrganizationInviteEmailAsync(organization.Name, orgUser, new ExpiringToken(token, now.AddDays(5)), initOrganization);
|
||||
}
|
||||
|
||||
public async Task<OrganizationUser> AcceptUserAsync(Guid organizationUserId, User user, string token,
|
||||
@ -2428,4 +2429,89 @@ public class OrganizationService : IOrganizationService
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
public async Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted)
|
||||
{
|
||||
var plan = StaticStore.Plans.FirstOrDefault(p => p.Type == organization.PlanType);
|
||||
if (plan is not { LegacyYear: null })
|
||||
{
|
||||
throw new BadRequestException("Invalid plan selected.");
|
||||
}
|
||||
|
||||
if (plan.Disabled)
|
||||
{
|
||||
throw new BadRequestException("Plan not found.");
|
||||
}
|
||||
|
||||
organization.Id = CoreHelpers.GenerateComb();
|
||||
organization.Enabled = false;
|
||||
organization.Status = OrganizationStatusType.Pending;
|
||||
|
||||
await SignUpAsync(organization, default, null, null, true);
|
||||
|
||||
var ownerOrganizationUser = new OrganizationUser
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
UserId = null,
|
||||
Email = ownerEmail,
|
||||
Key = null,
|
||||
Type = OrganizationUserType.Owner,
|
||||
Status = OrganizationUserStatusType.Invited,
|
||||
AccessAll = true
|
||||
};
|
||||
await _organizationUserRepository.CreateAsync(ownerOrganizationUser);
|
||||
|
||||
await SendInviteAsync(ownerOrganizationUser, organization, true);
|
||||
await _eventService.LogOrganizationUserEventAsync(ownerOrganizationUser, EventType.OrganizationUser_Invited);
|
||||
|
||||
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.OrganizationCreatedByAdmin, organization)
|
||||
{
|
||||
EventRaisedByUser = userService.GetUserName(user),
|
||||
SalesAssistedTrialStarted = salesAssistedTrialStarted,
|
||||
});
|
||||
}
|
||||
|
||||
public async Task InitPendingOrganization(Guid userId, Guid organizationId, string publicKey, string privateKey, string collectionName)
|
||||
{
|
||||
await ValidateSignUpPoliciesAsync(userId);
|
||||
|
||||
var org = await GetOrgById(organizationId);
|
||||
|
||||
if (org.Enabled)
|
||||
{
|
||||
throw new BadRequestException("Organization is already enabled.");
|
||||
}
|
||||
|
||||
if (org.Status != OrganizationStatusType.Pending)
|
||||
{
|
||||
throw new BadRequestException("Organization is not on a Pending status.");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(org.PublicKey))
|
||||
{
|
||||
throw new BadRequestException("Organization already has a Public Key.");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(org.PrivateKey))
|
||||
{
|
||||
throw new BadRequestException("Organization already has a Private Key.");
|
||||
}
|
||||
|
||||
org.Enabled = true;
|
||||
org.Status = OrganizationStatusType.Created;
|
||||
org.PublicKey = publicKey;
|
||||
org.PrivateKey = privateKey;
|
||||
|
||||
await UpdateAsync(org);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(collectionName))
|
||||
{
|
||||
var defaultCollection = new Collection
|
||||
{
|
||||
Name = collectionName,
|
||||
OrganizationId = org.Id
|
||||
};
|
||||
await _collectionRepository.CreateAsync(defaultCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -52,12 +52,12 @@ public class NoopMailService : IMailService
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token)
|
||||
public Task SendOrganizationInviteEmailAsync(string organizationName, OrganizationUser orgUser, ExpiringToken token, bool initOrganization = false)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites)
|
||||
public Task BulkSendOrganizationInviteEmailAsync(string organizationName, IEnumerable<(OrganizationUser orgUser, ExpiringToken token)> invites, bool initOrganization = false)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ public class NoopProviderService : IProviderService
|
||||
|
||||
public Task<List<Tuple<ProviderUser, string>>> DeleteUsersAsync(Guid providerId, IEnumerable<Guid> providerUserIds, Guid deletingUserId) => throw new NotImplementedException();
|
||||
|
||||
public Task AddOrganization(Guid providerId, Guid organizationId, Guid addingUserId, string key) => throw new NotImplementedException();
|
||||
public Task AddOrganization(Guid providerId, Guid organizationId, string key) => throw new NotImplementedException();
|
||||
|
||||
public Task AddOrganizationsToReseller(Guid providerId, IEnumerable<Guid> organizationIds) => throw new NotImplementedException();
|
||||
|
||||
|
Reference in New Issue
Block a user