diff --git a/src/Core/Models/Api/Request/Organizations/OrganizationUpdateRequestModel.cs b/src/Core/Models/Api/Request/Organizations/OrganizationUpdateRequestModel.cs index b70ade07a..4f92e8d66 100644 --- a/src/Core/Models/Api/Request/Organizations/OrganizationUpdateRequestModel.cs +++ b/src/Core/Models/Api/Request/Organizations/OrganizationUpdateRequestModel.cs @@ -10,6 +10,8 @@ namespace Bit.Core.Models.Api public string Name { get; set; } [StringLength(50)] public string BusinessName { get; set; } + [StringLength(50)] + public string Identifier { get; set; } [EmailAddress] [Required] [StringLength(50)] @@ -20,6 +22,7 @@ namespace Bit.Core.Models.Api existingOrganization.Name = Name; existingOrganization.BusinessName = BusinessName; existingOrganization.BillingEmail = BillingEmail?.ToLowerInvariant()?.Trim(); + existingOrganization.Identifier = Identifier; return existingOrganization; } } diff --git a/src/Core/Models/Api/Response/OrganizationResponseModel.cs b/src/Core/Models/Api/Response/OrganizationResponseModel.cs index 703fa3942..1c65ece17 100644 --- a/src/Core/Models/Api/Response/OrganizationResponseModel.cs +++ b/src/Core/Models/Api/Response/OrganizationResponseModel.cs @@ -18,6 +18,7 @@ namespace Bit.Core.Models.Api } Id = organization.Id.ToString(); + Identifier = organization.Identifier; Name = organization.Name; BusinessName = organization.BusinessName; BusinessAddress1 = organization.BusinessAddress1; @@ -44,6 +45,7 @@ namespace Bit.Core.Models.Api } public string Id { get; set; } + public string Identifier { get; set; } public string Name { get; set; } public string BusinessName { get; set; } public string BusinessAddress1 { get; set; } diff --git a/src/Core/Repositories/EntityFramework/OrganizationRepository.cs b/src/Core/Repositories/EntityFramework/OrganizationRepository.cs index 9db92f827..5670ef843 100644 --- a/src/Core/Repositories/EntityFramework/OrganizationRepository.cs +++ b/src/Core/Repositories/EntityFramework/OrganizationRepository.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using AutoMapper; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; +using Bit.Core.Models.Table; namespace Bit.Core.Repositories.EntityFramework { @@ -17,6 +18,17 @@ namespace Bit.Core.Repositories.EntityFramework : base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Organizations) { } + public async Task GetByIdentifierAsync(string identifier) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var dbContext = GetDatabaseContext(scope); + var organization = await GetDbSet(dbContext).Where(e => e.Identifier == identifier) + .FirstOrDefaultAsync(); + return organization; + } + } + public async Task> GetManyByEnabledAsync() { using (var scope = ServiceScopeFactory.CreateScope()) diff --git a/src/Core/Repositories/IOrganizationRepository.cs b/src/Core/Repositories/IOrganizationRepository.cs index 496283f6b..bebfc51a7 100644 --- a/src/Core/Repositories/IOrganizationRepository.cs +++ b/src/Core/Repositories/IOrganizationRepository.cs @@ -8,6 +8,7 @@ namespace Bit.Core.Repositories { public interface IOrganizationRepository : IRepository { + Task GetByIdentifierAsync(string identifier); Task> GetManyByEnabledAsync(); Task> GetManyByUserIdAsync(Guid userId); Task> SearchAsync(string name, string userEmail, bool? paid, int skip, int take); diff --git a/src/Core/Repositories/SqlServer/OrganizationRepository.cs b/src/Core/Repositories/SqlServer/OrganizationRepository.cs index 528575d61..c1433ab96 100644 --- a/src/Core/Repositories/SqlServer/OrganizationRepository.cs +++ b/src/Core/Repositories/SqlServer/OrganizationRepository.cs @@ -20,6 +20,18 @@ namespace Bit.Core.Repositories.SqlServer : base(connectionString, readOnlyConnectionString) { } + public async Task GetByIdentifierAsync(string identifier) + { + using (var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + "[dbo].[Organization_ReadByIdentifier]", + commandType: CommandType.StoredProcedure); + + return results.SingleOrDefault(); + } + } + public async Task> GetManyByEnabledAsync() { using (var connection = new SqlConnection(ConnectionString)) diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index f43570068..794690159 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -885,6 +885,15 @@ namespace Bit.Core.Services throw new ApplicationException("Cannot create org this way. Call SignUpAsync."); } + if (!string.IsNullOrWhiteSpace(organization.Identifier)) + { + var orgById = await _organizationRepository.GetByIdentifierAsync(organization.Identifier); + if (orgById != null && orgById.Id != organization.Id) + { + throw new BadRequestException("Identifier already in use by another organization."); + } + } + await ReplaceAndUpdateCache(organization, EventType.Organization_Updated); if (updateBilling && !string.IsNullOrWhiteSpace(organization.GatewayCustomerId)) diff --git a/src/Sql/dbo/Stored Procedures/Organization_ReadByIdentifier.sql b/src/Sql/dbo/Stored Procedures/Organization_ReadByIdentifier.sql new file mode 100644 index 000000000..305396483 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Organization_ReadByIdentifier.sql @@ -0,0 +1,13 @@ +CREATE PROCEDURE [dbo].[Organization_ReadByIdentifier] + @Identifier UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[OrganizationView] + WHERE + [Identifier] = @Identifier +END \ No newline at end of file diff --git a/util/Migrator/DbScripts/2020-08-12_00_OrgIdentifierProc.sql b/util/Migrator/DbScripts/2020-08-12_00_OrgIdentifierProc.sql new file mode 100644 index 000000000..646a89aeb --- /dev/null +++ b/util/Migrator/DbScripts/2020-08-12_00_OrgIdentifierProc.sql @@ -0,0 +1,20 @@ +IF OBJECT_ID('[dbo].[Organization_ReadByIdentifier]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Organization_ReadByIdentifier] +END +GO + +CREATE PROCEDURE [dbo].[Organization_ReadByIdentifier] + @Identifier UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[OrganizationView] + WHERE + [Identifier] = @Identifier +END +GO