diff --git a/src/Core/Models/Business/OrganizationLicense.cs b/src/Core/Models/Business/OrganizationLicense.cs index 733f945c3..577e41f8e 100644 --- a/src/Core/Models/Business/OrganizationLicense.cs +++ b/src/Core/Models/Business/OrganizationLicense.cs @@ -19,7 +19,7 @@ namespace Bit.Core.Models.Business public OrganizationLicense(Organization org, BillingInfo billingInfo, Guid installationId, ILicensingService licenseService) { - Version = 3; + Version = 4; LicenseKey = org.LicenseKey; InstallationId = installationId; Id = org.Id; @@ -35,6 +35,7 @@ namespace Bit.Core.Models.Business UseEvents = org.UseEvents; UseDirectory = org.UseDirectory; UseTotp = org.UseTotp; + Use2fa = org.Use2fa; MaxStorageGb = org.MaxStorageGb; SelfHost = org.SelfHost; UsersGetPremium = org.UsersGetPremium; @@ -100,6 +101,7 @@ namespace Bit.Core.Models.Business public bool UseEvents { get; set; } public bool UseDirectory { get; set; } public bool UseTotp { get; set; } + public bool Use2fa { get; set; } public short? MaxStorageGb { get; set; } public bool SelfHost { get; set; } public bool UsersGetPremium { get; set; } @@ -116,7 +118,7 @@ namespace Bit.Core.Models.Business public byte[] GetDataBytes(bool forHash = false) { string data = null; - if(Version >= 1 && Version <= 3) + if(Version >= 1 && Version <= 4) { var props = typeof(OrganizationLicense) .GetProperties(BindingFlags.Public | BindingFlags.Instance) @@ -127,6 +129,8 @@ namespace Bit.Core.Models.Business (Version >= 2 || !p.Name.Equals(nameof(UsersGetPremium))) && // UseEvents was added in Version 3 (Version >= 3 || !p.Name.Equals(nameof(UseEvents))) && + // Use2fa was added in Version 4 + (Version >= 4 || !p.Name.Equals(nameof(Use2fa))) && ( !forHash || ( @@ -163,7 +167,7 @@ namespace Bit.Core.Models.Business return false; } - if(Version >= 1 && Version <= 3) + if(Version >= 1 && Version <= 4) { return InstallationId == globalSettings.Installation.Id && SelfHost; } @@ -180,7 +184,7 @@ namespace Bit.Core.Models.Business return false; } - if(Version >= 1 && Version <= 3) + if(Version >= 1 && Version <= 4) { var valid = globalSettings.Installation.Id == InstallationId && @@ -205,6 +209,11 @@ namespace Bit.Core.Models.Business valid = organization.UseEvents == UseEvents; } + if(valid && Version >= 4) + { + valid = organization.Use2fa == Use2fa; + } + return valid; } else diff --git a/src/Core/Models/Data/OrganizationAbility.cs b/src/Core/Models/Data/OrganizationAbility.cs index d90534987..1d13dcabd 100644 --- a/src/Core/Models/Data/OrganizationAbility.cs +++ b/src/Core/Models/Data/OrganizationAbility.cs @@ -11,11 +11,13 @@ namespace Bit.Core.Models.Data { Id = organization.Id; UseEvents = organization.UseEvents; + Use2fa = organization.Use2fa; Enabled = organization.Enabled; } public Guid Id { get; set; } public bool UseEvents { get; set; } + public bool Use2fa { get; set; } public bool Enabled { get; set; } } } diff --git a/src/Core/Models/StaticStore/Plan.cs b/src/Core/Models/StaticStore/Plan.cs index b3c35de61..e76c5a0d4 100644 --- a/src/Core/Models/StaticStore/Plan.cs +++ b/src/Core/Models/StaticStore/Plan.cs @@ -16,6 +16,7 @@ namespace Bit.Core.Models.StaticStore public bool UseDirectory { get; set; } public bool UseEvents { get; set; } public bool UseTotp { get; set; } + public bool Use2fa { get; set; } public short? MaxStorageGb { get; set; } public decimal BasePrice { get; set; } public decimal SeatPrice { get; set; } diff --git a/src/Core/Models/Table/Organization.cs b/src/Core/Models/Table/Organization.cs index f56fd398b..d13ac885e 100644 --- a/src/Core/Models/Table/Organization.cs +++ b/src/Core/Models/Table/Organization.cs @@ -3,11 +3,16 @@ using Bit.Core.Utilities; using Bit.Core.Enums; using Bit.Core.Services; using Bit.Core.Exceptions; +using System.Collections.Generic; +using Newtonsoft.Json; +using System.Linq; namespace Bit.Core.Models.Table { public class Organization : ITableObject, ISubscriber, IStorable, IStorableSubscriber, IRevisable { + private Dictionary _twoFactorProviders; + public Guid Id { get; set; } public string Name { get; set; } public string BusinessName { get; set; } @@ -25,6 +30,7 @@ namespace Bit.Core.Models.Table public bool UseDirectory { get; set; } public bool UseEvents { get; set; } public bool UseTotp { get; set; } + public bool Use2fa { get; set; } public bool SelfHost { get; set; } public bool UsersGetPremium { get; set; } public long? Storage { get; set; } @@ -34,6 +40,7 @@ namespace Bit.Core.Models.Table public string GatewaySubscriptionId { get; set; } public bool Enabled { get; set; } = true; public string LicenseKey { get; set; } + public string TwoFactorProviders { get; set; } public DateTime? ExpirationDate { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; @@ -99,5 +106,71 @@ namespace Bit.Core.Models.Table return paymentService; } + + public Dictionary GetTwoFactorProviders() + { + if(string.IsNullOrWhiteSpace(TwoFactorProviders)) + { + return null; + } + + try + { + if(_twoFactorProviders == null) + { + _twoFactorProviders = + JsonConvert.DeserializeObject>( + TwoFactorProviders); + } + + return _twoFactorProviders; + } + catch(JsonSerializationException) + { + return null; + } + } + + public void SetTwoFactorProviders(Dictionary providers) + { + TwoFactorProviders = JsonConvert.SerializeObject(providers, new JsonSerializerSettings + { + ContractResolver = new EnumKeyResolver() + }); + _twoFactorProviders = providers; + } + + public bool TwoFactorProviderIsEnabled(TwoFactorProviderType provider) + { + var providers = GetTwoFactorProviders(); + if(providers == null || !providers.ContainsKey(provider)) + { + return false; + } + + return providers[provider].Enabled && Use2fa; + } + + public bool TwoFactorIsEnabled() + { + var providers = GetTwoFactorProviders(); + if(providers == null) + { + return false; + } + + return providers.Any(p => (p.Value?.Enabled ?? false) && Use2fa); + } + + public TwoFactorProvider GetTwoFactorProvider(TwoFactorProviderType provider) + { + var providers = GetTwoFactorProviders(); + if(providers == null || !providers.ContainsKey(provider)) + { + return null; + } + + return providers[provider]; + } } } diff --git a/src/Core/Models/Table/User.cs b/src/Core/Models/Table/User.cs index 80e1625ed..1868d1bc6 100644 --- a/src/Core/Models/Table/User.cs +++ b/src/Core/Models/Table/User.cs @@ -68,7 +68,8 @@ namespace Bit.Core.Models.Table if(_twoFactorProviders == null) { _twoFactorProviders = - JsonConvert.DeserializeObject>(TwoFactorProviders); + JsonConvert.DeserializeObject>( + TwoFactorProviders); } return _twoFactorProviders; @@ -107,7 +108,8 @@ namespace Bit.Core.Models.Table return false; } - return providers.Any(p => (p.Value?.Enabled ?? false) && (Premium || !TwoFactorProvider.RequiresPremium(p.Key))); + return providers.Any(p => (p.Value?.Enabled ?? false) && + (Premium || !TwoFactorProvider.RequiresPremium(p.Key))); } public TwoFactorProvider GetTwoFactorProvider(TwoFactorProviderType provider) diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 716fb00ca..07cd02456 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -533,6 +533,7 @@ namespace Bit.Core.Services UseEvents = plan.UseEvents, UseDirectory = plan.UseDirectory, UseTotp = plan.UseTotp, + Use2fa = plan.Use2fa, SelfHost = plan.SelfHost, UsersGetPremium = plan.UsersGetPremium, Plan = plan.Name, @@ -588,6 +589,7 @@ namespace Bit.Core.Services UseDirectory = license.UseDirectory, UseEvents = license.UseEvents, UseTotp = license.UseTotp, + Use2fa = license.Use2fa, Plan = license.Plan, SelfHost = license.SelfHost, UsersGetPremium = license.UsersGetPremium, @@ -753,6 +755,9 @@ namespace Bit.Core.Services organization.UseDirectory = license.UseDirectory; organization.UseEvents = license.UseEvents; organization.UseTotp = license.UseTotp; + organization.Use2fa = license.Use2fa; + organization.SelfHost = license.SelfHost; + organization.UsersGetPremium = license.UsersGetPremium; organization.Plan = license.Plan; organization.Enabled = license.Enabled; organization.ExpirationDate = license.Expires; diff --git a/src/Core/Utilities/StaticStore.cs b/src/Core/Utilities/StaticStore.cs index 7f6dc81f1..eda7e0f88 100644 --- a/src/Core/Utilities/StaticStore.cs +++ b/src/Core/Utilities/StaticStore.cs @@ -166,6 +166,7 @@ namespace Bit.Core.Utilities UseDirectory = true, UseEvents = true, UseTotp = true, + Use2fa = true, MaxStorageGb = 1, SelfHost = true, UsersGetPremium = true @@ -187,6 +188,7 @@ namespace Bit.Core.Utilities UseDirectory = true, UseEvents = true, UseTotp = true, + Use2fa = true, MaxStorageGb = 1, SelfHost = true, UsersGetPremium = true diff --git a/src/Sql/dbo/Stored Procedures/Organization_Create.sql b/src/Sql/dbo/Stored Procedures/Organization_Create.sql index c66572aaf..a12b2bf11 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Create.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Create.sql @@ -16,6 +16,7 @@ @UseDirectory BIT, @UseEvents BIT, @UseTotp BIT, + @Use2fa BIT, @SelfHost BIT, @UsersGetPremium BIT, @Storage BIGINT, @@ -25,6 +26,7 @@ @GatewaySubscriptionId VARCHAR(50), @Enabled BIT, @LicenseKey VARCHAR(100), + @TwoFactorProviders NVARCHAR(MAX), @ExpirationDate DATETIME2(7), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7) @@ -51,6 +53,7 @@ BEGIN [UseDirectory], [UseEvents], [UseTotp], + [Use2fa], [SelfHost], [UsersGetPremium], [Storage], @@ -60,6 +63,7 @@ BEGIN [GatewaySubscriptionId], [Enabled], [LicenseKey], + [TwoFactorProviders], [ExpirationDate], [CreationDate], [RevisionDate] @@ -83,6 +87,7 @@ BEGIN @UseDirectory, @UseEvents, @UseTotp, + @Use2fa, @SelfHost, @UsersGetPremium, @Storage, @@ -92,6 +97,7 @@ BEGIN @GatewaySubscriptionId, @Enabled, @LicenseKey, + @TwoFactorProviders, @ExpirationDate, @CreationDate, @RevisionDate diff --git a/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql b/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql index a067bb61d..bad960a63 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql @@ -6,6 +6,7 @@ BEGIN SELECT [Id], [UseEvents], + [Use2fa], [Enabled] FROM [dbo].[Organization] diff --git a/src/Sql/dbo/Stored Procedures/Organization_Update.sql b/src/Sql/dbo/Stored Procedures/Organization_Update.sql index 47252610c..bd933c9bb 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_Update.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_Update.sql @@ -16,6 +16,7 @@ @UseDirectory BIT, @UseEvents BIT, @UseTotp BIT, + @Use2fa BIT, @SelfHost BIT, @UsersGetPremium BIT, @Storage BIGINT, @@ -25,6 +26,7 @@ @GatewaySubscriptionId VARCHAR(50), @Enabled BIT, @LicenseKey VARCHAR(100), + @TwoFactorProviders NVARCHAR(MAX), @ExpirationDate DATETIME2(7), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7) @@ -51,6 +53,7 @@ BEGIN [UseDirectory] = @UseDirectory, [UseEvents] = @UseEvents, [UseTotp] = @UseTotp, + [Use2fa] = @Use2fa, [SelfHost] = @SelfHost, [UsersGetPremium] = @UsersGetPremium, [Storage] = @Storage, @@ -60,6 +63,7 @@ BEGIN [GatewaySubscriptionId] = @GatewaySubscriptionId, [Enabled] = @Enabled, [LicenseKey] = @LicenseKey, + [TwoFactorProviders] = @TwoFactorProviders, [ExpirationDate] = @ExpirationDate, [CreationDate] = @CreationDate, [RevisionDate] = @RevisionDate diff --git a/src/Sql/dbo/Tables/Organization.sql b/src/Sql/dbo/Tables/Organization.sql index 6c6a19aac..5f2dffcea 100644 --- a/src/Sql/dbo/Tables/Organization.sql +++ b/src/Sql/dbo/Tables/Organization.sql @@ -16,6 +16,7 @@ [UseDirectory] BIT NOT NULL, [UseEvents] BIT NOT NULL, [UseTotp] BIT NOT NULL, + [Use2fa] BIT NOT NULL, [SelfHost] BIT NOT NULL, [UsersGetPremium] BIT NOT NULL, [Storage] BIGINT NULL, @@ -25,6 +26,7 @@ [GatewaySubscriptionId] VARCHAR (50) NULL, [Enabled] BIT NOT NULL, [LicenseKey] VARCHAR (100) NULL, + [TwoFactorProviders] NVARCHAR (MAX) NULL, [ExpirationDate] DATETIME2 (7) NULL, [CreationDate] DATETIME2 (7) NOT NULL, [RevisionDate] DATETIME2 (7) NOT NULL, diff --git a/util/Setup/DbScripts/2018-04-02_00_Org2fa.sql b/util/Setup/DbScripts/2018-04-02_00_Org2fa.sql new file mode 100644 index 000000000..246a591a5 --- /dev/null +++ b/util/Setup/DbScripts/2018-04-02_00_Org2fa.sql @@ -0,0 +1,243 @@ +IF COL_LENGTH('[dbo].[Organization]', 'Use2fa') IS NULL +BEGIN + ALTER TABLE + [dbo].[Organization] + ADD + [Use2fa] BIT NULL +END +GO + +UPDATE + [dbo].[Organization] +SET + [Use2fa] = (CASE WHEN [PlanType] = 5 OR [PlanType] = 4 THEN 1 ELSE 0 END) +GO + +ALTER TABLE + [dbo].[Organization] +ALTER COLUMN + [Use2fa] BIT NOT NULL +GO + +IF COL_LENGTH('[dbo].[Organization]', 'TwoFactorProviders') IS NULL +BEGIN + ALTER TABLE + [dbo].[Organization] + ADD + [TwoFactorProviders] NVARCHAR(MAX) NULL +END +GO + +IF OBJECT_ID('[dbo].[Organization_Create]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Organization_Create] +END +GO + +CREATE PROCEDURE [dbo].[Organization_Create] + @Id UNIQUEIDENTIFIER, + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(50), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats SMALLINT, + @MaxCollections SMALLINT, + @UseGroups BIT, + @UseDirectory BIT, + @UseEvents BIT, + @UseTotp BIT, + @Use2fa BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @TwoFactorProviders NVARCHAR(MAX), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[Organization] + ( + [Id], + [Name], + [BusinessName], + [BusinessAddress1], + [BusinessAddress2], + [BusinessAddress3], + [BusinessCountry], + [BusinessTaxNumber], + [BillingEmail], + [Plan], + [PlanType], + [Seats], + [MaxCollections], + [UseGroups], + [UseDirectory], + [UseEvents], + [UseTotp], + [Use2fa], + [SelfHost], + [UsersGetPremium], + [Storage], + [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [Enabled], + [LicenseKey], + [TwoFactorProviders], + [ExpirationDate], + [CreationDate], + [RevisionDate] + ) + VALUES + ( + @Id, + @Name, + @BusinessName, + @BusinessAddress1, + @BusinessAddress2, + @BusinessAddress3, + @BusinessCountry, + @BusinessTaxNumber, + @BillingEmail, + @Plan, + @PlanType, + @Seats, + @MaxCollections, + @UseGroups, + @UseDirectory, + @UseEvents, + @UseTotp, + @Use2fa, + @SelfHost, + @UsersGetPremium, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @Enabled, + @LicenseKey, + @TwoFactorProviders, + @ExpirationDate, + @CreationDate, + @RevisionDate + ) +END +GO + +IF OBJECT_ID('[dbo].[Organization_Update]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Organization_Update] +END +GO + +CREATE PROCEDURE [dbo].[Organization_Update] + @Id UNIQUEIDENTIFIER, + @Name NVARCHAR(50), + @BusinessName NVARCHAR(50), + @BusinessAddress1 NVARCHAR(50), + @BusinessAddress2 NVARCHAR(50), + @BusinessAddress3 NVARCHAR(50), + @BusinessCountry VARCHAR(2), + @BusinessTaxNumber NVARCHAR(30), + @BillingEmail NVARCHAR(50), + @Plan NVARCHAR(50), + @PlanType TINYINT, + @Seats SMALLINT, + @MaxCollections SMALLINT, + @UseGroups BIT, + @UseDirectory BIT, + @UseEvents BIT, + @UseTotp BIT, + @Use2fa BIT, + @SelfHost BIT, + @UsersGetPremium BIT, + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @Enabled BIT, + @LicenseKey VARCHAR(100), + @TwoFactorProviders NVARCHAR(MAX), + @ExpirationDate DATETIME2(7), + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7) +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[Organization] + SET + [Name] = @Name, + [BusinessName] = @BusinessName, + [BusinessAddress1] = @BusinessAddress1, + [BusinessAddress2] = @BusinessAddress2, + [BusinessAddress3] = @BusinessAddress3, + [BusinessCountry] = @BusinessCountry, + [BusinessTaxNumber] = @BusinessTaxNumber, + [BillingEmail] = @BillingEmail, + [Plan] = @Plan, + [PlanType] = @PlanType, + [Seats] = @Seats, + [MaxCollections] = @MaxCollections, + [UseGroups] = @UseGroups, + [UseDirectory] = @UseDirectory, + [UseEvents] = @UseEvents, + [UseTotp] = @UseTotp, + [Use2fa] = @Use2fa, + [SelfHost] = @SelfHost, + [UsersGetPremium] = @UsersGetPremium, + [Storage] = @Storage, + [MaxStorageGb] = @MaxStorageGb, + [Gateway] = @Gateway, + [GatewayCustomerId] = @GatewayCustomerId, + [GatewaySubscriptionId] = @GatewaySubscriptionId, + [Enabled] = @Enabled, + [LicenseKey] = @LicenseKey, + [TwoFactorProviders] = @TwoFactorProviders, + [ExpirationDate] = @ExpirationDate, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate + WHERE + [Id] = @Id +END +GO + +IF OBJECT_ID('[dbo].[Organization_ReadAbilities]') IS NOT NULL +BEGIN + DROP PROCEDURE [dbo].[Organization_ReadAbilities] +END +GO + +CREATE PROCEDURE [dbo].[Organization_ReadAbilities] +AS +BEGIN + SET NOCOUNT ON + + SELECT + [Id], + [UseEvents], + [Use2fa], + [Enabled] + FROM + [dbo].[Organization] +END +GO diff --git a/util/Setup/Setup.csproj b/util/Setup/Setup.csproj index dd960b081..83fdaf665 100644 --- a/util/Setup/Setup.csproj +++ b/util/Setup/Setup.csproj @@ -8,6 +8,11 @@ + + + + +