1
0
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:
Rui Tomé
2023-03-30 11:37:41 +01:00
committed by GitHub
parent 7887690c09
commit 570d239da9
19 changed files with 227 additions and 38 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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>

View File

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

View File

@ -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("")]

View File

@ -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]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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