diff --git a/bitwarden_license/src/Commercial.Core/Services/ProviderService.cs b/bitwarden_license/src/Commercial.Core/Services/ProviderService.cs index ef9c7c7840..7062655583 100644 --- a/bitwarden_license/src/Commercial.Core/Services/ProviderService.cs +++ b/bitwarden_license/src/Commercial.Core/Services/ProviderService.cs @@ -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) diff --git a/bitwarden_license/test/Commercial.Core.Test/Services/ProviderServiceTests.cs b/bitwarden_license/test/Commercial.Core.Test/Services/ProviderServiceTests.cs index 218cb5c157..2d525cc5b4 100644 --- a/bitwarden_license/test/Commercial.Core.Test/Services/ProviderServiceTests.cs +++ b/bitwarden_license/test/Commercial.Core.Test/Services/ProviderServiceTests.cs @@ -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 sutProvider) { po.OrganizationId = organization.Id; @@ -427,12 +427,12 @@ public class ProviderServiceTests .Returns(po); var exception = await Assert.ThrowsAsync( - () => 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 sutProvider) { organization.PlanType = PlanType.EnterpriseAnnually; @@ -442,7 +442,7 @@ public class ProviderServiceTests providerOrganizationRepository.GetByOrganizationId(organization.Id).ReturnsNull(); sutProvider.GetDependency().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() diff --git a/src/Admin/Controllers/OrganizationsController.cs b/src/Admin/Controllers/OrganizationsController.cs index 8829db39aa..9a07dd11c4 100644 --- a/src/Admin/Controllers/OrganizationsController.cs +++ b/src/Admin/Controllers/OrganizationsController.cs @@ -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 _logger; public OrganizationsController( + IOrganizationService organizationService, IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, IOrganizationConnectionRepository organizationConnectionRepository, @@ -52,6 +54,7 @@ public class OrganizationsController : Controller IProviderRepository providerRepository, ILogger logger) { + _organizationService = organizationService; _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; _organizationConnectionRepository = organizationConnectionRepository; @@ -219,4 +222,21 @@ public class OrganizationsController : Controller return RedirectToAction("Index"); } + [HttpPost] + public async Task 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); + } } diff --git a/src/Admin/Controllers/ProvidersController.cs b/src/Admin/Controllers/ProvidersController.cs index e0826d7776..25c5a3a802 100644 --- a/src/Admin/Controllers/ProvidersController.cs +++ b/src/Admin/Controllers/ProvidersController.cs @@ -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 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 }); } diff --git a/src/Admin/Models/OrganizationEditModel.cs b/src/Admin/Models/OrganizationEditModel.cs index cb44e1725c..f67ad18abc 100644 --- a/src/Admin/Models/OrganizationEditModel.cs +++ b/src/Admin/Models/OrganizationEditModel.cs @@ -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; diff --git a/src/Admin/Views/Providers/Organizations.cshtml b/src/Admin/Views/Providers/Organizations.cshtml index 1c11c87ad2..a6cc20a1c8 100644 --- a/src/Admin/Views/Providers/Organizations.cshtml +++ b/src/Admin/Views/Providers/Organizations.cshtml @@ -1,4 +1,7 @@ @model ProviderViewModel + +@await Html.PartialAsync("_ProviderScripts") +

Provider Organizations

@@ -41,7 +44,7 @@
@if (org.Status == OrganizationStatusType.Pending) { - + } diff --git a/src/Admin/Views/Providers/_ProviderScripts.cshtml b/src/Admin/Views/Providers/_ProviderScripts.cshtml new file mode 100644 index 0000000000..d74065d562 --- /dev/null +++ b/src/Admin/Views/Providers/_ProviderScripts.cshtml @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/src/Api/Controllers/OrganizationUsersController.cs b/src/Api/Controllers/OrganizationUsersController.cs index e320aca768..255efdc9e4 100644 --- a/src/Api/Controllers/OrganizationUsersController.cs +++ b/src/Api/Controllers/OrganizationUsersController.cs @@ -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().AutoEnrollEnabled; - - if (useMasterPasswordPolicy && - string.IsNullOrWhiteSpace(model.ResetPasswordKey)) + masterPasswordPolicy.Enabled && + masterPasswordPolicy.GetDataModel().AutoEnrollEnabled; + if (useMasterPasswordPolicy && string.IsNullOrWhiteSpace(model.ResetPasswordKey)) { throw new BadRequestException(string.Empty, "Master Password reset is required, but not provided."); } diff --git a/src/Api/Controllers/ProviderOrganizationsController.cs b/src/Api/Controllers/ProviderOrganizationsController.cs index 222d11302e..6f82832802 100644 --- a/src/Api/Controllers/ProviderOrganizationsController.cs +++ b/src/Api/Controllers/ProviderOrganizationsController.cs @@ -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("")] diff --git a/src/Api/Models/Request/Organizations/OrganizationUserRequestModels.cs b/src/Api/Models/Request/Organizations/OrganizationUserRequestModels.cs index 9a8ab0192c..a702ee7c94 100644 --- a/src/Api/Models/Request/Organizations/OrganizationUserRequestModels.cs +++ b/src/Api/Models/Request/Organizations/OrganizationUserRequestModels.cs @@ -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] diff --git a/src/Core/Enums/ReferenceEventType.cs b/src/Core/Enums/ReferenceEventType.cs index 1a925736c4..4e5134a2e5 100644 --- a/src/Core/Enums/ReferenceEventType.cs +++ b/src/Core/Enums/ReferenceEventType.cs @@ -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 } diff --git a/src/Core/Models/Mail/OrganizationUserInvitedViewModel.cs b/src/Core/Models/Mail/OrganizationUserInvitedViewModel.cs index 4bf9fbb863..5f10db74a5 100644 --- a/src/Core/Models/Mail/OrganizationUserInvitedViewModel.cs +++ b/src/Core/Models/Mail/OrganizationUserInvitedViewModel.cs @@ -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); } diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index 9c86a445d5..59c3056d2d 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -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 ownerEmails); Task SendOrganizationAutoscaledEmailAsync(Organization organization, int initialSeatCount, IEnumerable ownerEmails); Task SendOrganizationAcceptedEmailAsync(Organization organization, string userIdentifier, IEnumerable adminEmails); diff --git a/src/Core/Services/IOrganizationService.cs b/src/Core/Services/IOrganizationService.cs index 8082ed0f44..81afdde47b 100644 --- a/src/Core/Services/IOrganizationService.cs +++ b/src/Core/Services/IOrganizationService.cs @@ -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 InviteUserAsync(Guid organizationId, EventSystemUser systemUser, string email, OrganizationUserType type, bool accessAll, string externalId, IEnumerable collections, IEnumerable groups); Task>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, IEnumerable organizationUsersId); - Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId); - Task AcceptUserAsync(Guid organizationUserId, User user, string token, - IUserService userService); + Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false); + Task AcceptUserAsync(Guid organizationUserId, User user, string token, IUserService userService); Task AcceptUserAsync(string orgIdentifier, User user, IUserService userService); Task 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>> RestoreUsersAsync(Guid organizationId, IEnumerable organizationUserIds, Guid? restoringUserId, IUserService userService); + Task CreatePendingOrganization(Organization organization, string ownerEmail, ClaimsPrincipal user, IUserService userService, bool salesAssistedTrialStarted); + /// + /// Update an Organization entry by setting the public/private keys, set it as 'Enabled' and move the Status from 'Pending' to 'Created'. + /// + /// + /// This method must target a disabled Organization that has null keys and status as 'Pending'. + /// + Task InitPendingOrganization(Guid userId, Guid organizationId, string publicKey, string privateKey, string collectionName); Task ReplaceAndUpdateCacheAsync(Organization org, EventType? orgEvent = null); } diff --git a/src/Core/Services/IProviderService.cs b/src/Core/Services/IProviderService.cs index a55eb8e1e8..c1ac8b5a33 100644 --- a/src/Core/Services/IProviderService.cs +++ b/src/Core/Services/IProviderService.cs @@ -19,7 +19,7 @@ public interface IProviderService Task>> DeleteUsersAsync(Guid providerId, IEnumerable 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 organizationIds); Task CreateOrganizationAsync(Guid providerId, OrganizationSignup organizationSignup, string clientOwnerEmail, User user); diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index 89edb7f097..4c85fc730e 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -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 } )); diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index fd81abf57c..4de6043c13 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -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 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 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); + } + } } diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index 9d7b4698ae..e4db9cf3e6 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -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); } diff --git a/src/Core/Services/NoopImplementations/NoopProviderService.cs b/src/Core/Services/NoopImplementations/NoopProviderService.cs index c6c98430d0..596ec91718 100644 --- a/src/Core/Services/NoopImplementations/NoopProviderService.cs +++ b/src/Core/Services/NoopImplementations/NoopProviderService.cs @@ -23,7 +23,7 @@ public class NoopProviderService : IProviderService public Task>> DeleteUsersAsync(Guid providerId, IEnumerable 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 organizationIds) => throw new NotImplementedException();