From 4585af5a859b59b43ef1f5419e7a9f9c1d03af6a Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 17 Aug 2017 00:12:11 -0400 Subject: [PATCH] validate organization licenses --- src/Core/IdentityServer/ProfileService.cs | 4 ++- .../Models/Business/OrganizationLicense.cs | 10 +++--- .../Repositories/IOrganizationRepository.cs | 1 + .../SqlServer/OrganizationRepository.cs | 12 +++++++ src/Core/Services/ILicensingService.cs | 4 +-- .../Implementations/OrganizationService.cs | 4 +-- .../Implementations/RsaLicensingService.cs | 35 ++++++++++++++----- .../NoopLicensingService.cs | 2 +- .../Stored Procedures/Organization_Read.sql | 11 ++++++ 9 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 src/Sql/dbo/Stored Procedures/Organization_Read.sql diff --git a/src/Core/IdentityServer/ProfileService.cs b/src/Core/IdentityServer/ProfileService.cs index 2153d55129..324d3313da 100644 --- a/src/Core/IdentityServer/ProfileService.cs +++ b/src/Core/IdentityServer/ProfileService.cs @@ -37,13 +37,15 @@ namespace Bit.Core.IdentityServer public async Task GetProfileDataAsync(ProfileDataRequestContext context) { + await _licensingService.ValidateOrganizationsAsync(); + var existingClaims = context.Subject.Claims; var newClaims = new List(); var user = await _userService.GetUserByPrincipalAsync(context.Subject); if(user != null) { - var isPremium = await _licensingService.VerifyUserPremiumAsync(user); + var isPremium = await _licensingService.ValidateUserPremiumAsync(user); newClaims.AddRange(new List { new Claim("premium", isPremium ? "true" : "false", ClaimValueTypes.Boolean), diff --git a/src/Core/Models/Business/OrganizationLicense.cs b/src/Core/Models/Business/OrganizationLicense.cs index da067f9883..56c1f82624 100644 --- a/src/Core/Models/Business/OrganizationLicense.cs +++ b/src/Core/Models/Business/OrganizationLicense.cs @@ -140,7 +140,7 @@ namespace Bit.Core.Models.Business } } - public bool CanUse(Guid installationId) + public bool CanUse(GlobalSettings globalSettings) { if(!Enabled || Issued > DateTime.UtcNow || Expires < DateTime.UtcNow) { @@ -149,7 +149,7 @@ namespace Bit.Core.Models.Business if(Version == 1) { - return InstallationId == installationId && SelfHost; + return InstallationId == globalSettings.Installation.Id && SelfHost; } else { @@ -157,7 +157,7 @@ namespace Bit.Core.Models.Business } } - public bool VerifyData(Organization organization) + public bool VerifyData(Organization organization, GlobalSettings globalSettings) { if(Issued > DateTime.UtcNow || Expires < DateTime.UtcNow) { @@ -167,6 +167,7 @@ namespace Bit.Core.Models.Business if(Version == 1) { return + globalSettings.Installation.Id == InstallationId && organization.LicenseKey.Equals(LicenseKey) && organization.Enabled == Enabled && organization.PlanType == PlanType && @@ -175,7 +176,8 @@ namespace Bit.Core.Models.Business organization.UseGroups == UseGroups && organization.UseDirectory == UseDirectory && organization.UseTotp == UseTotp && - organization.SelfHost == SelfHost; + organization.SelfHost == SelfHost && + organization.Name.Equals(Name); } else { diff --git a/src/Core/Repositories/IOrganizationRepository.cs b/src/Core/Repositories/IOrganizationRepository.cs index 3edf16606f..8b344bc9a0 100644 --- a/src/Core/Repositories/IOrganizationRepository.cs +++ b/src/Core/Repositories/IOrganizationRepository.cs @@ -7,6 +7,7 @@ namespace Bit.Core.Repositories { public interface IOrganizationRepository : IRepository { + Task> GetManyAsync(); Task> GetManyByUserIdAsync(Guid userId); Task UpdateStorageAsync(Guid id); } diff --git a/src/Core/Repositories/SqlServer/OrganizationRepository.cs b/src/Core/Repositories/SqlServer/OrganizationRepository.cs index e5fe47102b..c36c733662 100644 --- a/src/Core/Repositories/SqlServer/OrganizationRepository.cs +++ b/src/Core/Repositories/SqlServer/OrganizationRepository.cs @@ -19,6 +19,18 @@ namespace Bit.Core.Repositories.SqlServer : base(connectionString) { } + public async Task> GetManyAsync() + { + using(var connection = new SqlConnection(ConnectionString)) + { + var results = await connection.QueryAsync( + "[dbo].[Organization_Read]", + commandType: CommandType.StoredProcedure); + + return results.ToList(); + } + } + public async Task> GetManyByUserIdAsync(Guid userId) { using(var connection = new SqlConnection(ConnectionString)) diff --git a/src/Core/Services/ILicensingService.cs b/src/Core/Services/ILicensingService.cs index bce8aa90d3..5c978a9eb8 100644 --- a/src/Core/Services/ILicensingService.cs +++ b/src/Core/Services/ILicensingService.cs @@ -6,8 +6,8 @@ namespace Bit.Core.Services { public interface ILicensingService { - bool VerifyOrganizationPlan(Organization organization); - Task VerifyUserPremiumAsync(User user); + Task ValidateOrganizationsAsync(); + Task ValidateUserPremiumAsync(User user); bool VerifyLicense(ILicense license); byte[] SignLicense(ILicense license); } diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index 4ee828148c..6983cb66f8 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -548,7 +548,7 @@ namespace Bit.Core.Services throw new BadRequestException("Invalid license."); } - if(!license.CanUse(_globalSettings.Installation.Id)) + if(!license.CanUse(_globalSettings)) { throw new BadRequestException("Invalid license. Make sure your license allows for on-premise " + "hosting of organizations and that the installation id matches your current installation."); @@ -655,7 +655,7 @@ namespace Bit.Core.Services throw new BadRequestException("Invalid license."); } - if(!license.CanUse(_globalSettings.Installation.Id)) + if(!license.CanUse(_globalSettings)) { throw new BadRequestException("Invalid license. Make sure your license allows for on-premise " + "hosting of organizations and that the installation id matches your current installation."); diff --git a/src/Core/Services/Implementations/RsaLicensingService.cs b/src/Core/Services/Implementations/RsaLicensingService.cs index b65417cf6b..0a4203f36c 100644 --- a/src/Core/Services/Implementations/RsaLicensingService.cs +++ b/src/Core/Services/Implementations/RsaLicensingService.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; @@ -17,16 +18,20 @@ namespace Bit.Core.Services { private readonly X509Certificate2 _certificate; private readonly GlobalSettings _globalSettings; - private IDictionary _userCheckCache = new Dictionary(); - private IDictionary _organizationCheckCache = new Dictionary(); private readonly IUserRepository _userRepository; + private readonly IOrganizationRepository _organizationRepository; + + private IDictionary _userCheckCache = new Dictionary(); + private DateTime? _organizationCheckCache = null; public RsaLicensingService( IUserRepository userRepository, + IOrganizationRepository organizationRepository, IHostingEnvironment environment, GlobalSettings globalSettings) { _userRepository = userRepository; + _organizationRepository = organizationRepository; var certThumbprint = "‎207e64a231e8aa32aaf68a61037c075ebebd553f"; _globalSettings = globalSettings; @@ -44,23 +49,35 @@ namespace Bit.Core.Services } } - public bool VerifyOrganizationPlan(Organization organization) + public async Task ValidateOrganizationsAsync() { if(!_globalSettings.SelfHosted) { - return true; + return; } - if(!organization.SelfHost) + var now = DateTime.UtcNow; + if(_organizationCheckCache.HasValue && now - _organizationCheckCache.Value < TimeSpan.FromDays(1)) { - return false; + return; } + _organizationCheckCache = now; - var license = ReadOrganiztionLicense(organization); - return license != null && license.VerifyData(organization) && license.VerifySignature(_certificate); + var orgs = await _organizationRepository.GetManyAsync(); + foreach(var org in orgs.Where(o => o.Enabled)) + { + var license = ReadOrganiztionLicense(org); + if(license == null || !license.VerifyData(org, _globalSettings) || !license.VerifySignature(_certificate)) + { + org.Enabled = false; + org.ExpirationDate = license.Expires; + org.RevisionDate = DateTime.UtcNow; + await _organizationRepository.ReplaceAsync(org); + } + } } - public async Task VerifyUserPremiumAsync(User user) + public async Task ValidateUserPremiumAsync(User user) { if(!_globalSettings.SelfHosted) { diff --git a/src/Core/Services/NoopImplementations/NoopLicensingService.cs b/src/Core/Services/NoopImplementations/NoopLicensingService.cs index 9494ebb50a..a8fb0fce11 100644 --- a/src/Core/Services/NoopImplementations/NoopLicensingService.cs +++ b/src/Core/Services/NoopImplementations/NoopLicensingService.cs @@ -28,7 +28,7 @@ namespace Bit.Core.Services return true; } - public Task VerifyUserPremiumAsync(User user) + public Task ValidateUserPremiumAsync(User user) { return Task.FromResult(user.Premium); } diff --git a/src/Sql/dbo/Stored Procedures/Organization_Read.sql b/src/Sql/dbo/Stored Procedures/Organization_Read.sql new file mode 100644 index 0000000000..7fcfe97439 --- /dev/null +++ b/src/Sql/dbo/Stored Procedures/Organization_Read.sql @@ -0,0 +1,11 @@ +CREATE PROCEDURE [dbo].[Organization_Read] + @Id UNIQUEIDENTIFIER +AS +BEGIN + SET NOCOUNT ON + + SELECT + * + FROM + [dbo].[OrganizationView] +END \ No newline at end of file