diff --git a/src/Core/IdentityServer/ProfileService.cs b/src/Core/IdentityServer/ProfileService.cs index 1359e126c..2153d5512 100644 --- a/src/Core/IdentityServer/ProfileService.cs +++ b/src/Core/IdentityServer/ProfileService.cs @@ -18,17 +18,20 @@ namespace Bit.Core.IdentityServer private readonly IUserService _userService; private readonly IUserRepository _userRepository; private readonly IOrganizationUserRepository _organizationUserRepository; + private readonly ILicensingService _licensingService; private IdentityOptions _identityOptions; public ProfileService( IUserRepository userRepository, IUserService userService, IOrganizationUserRepository organizationUserRepository, + ILicensingService licensingService, IOptions identityOptionsAccessor) { _userRepository = userRepository; _userService = userService; _organizationUserRepository = organizationUserRepository; + _licensingService = licensingService; _identityOptions = identityOptionsAccessor?.Value ?? new IdentityOptions(); } @@ -40,9 +43,10 @@ namespace Bit.Core.IdentityServer var user = await _userService.GetUserByPrincipalAsync(context.Subject); if(user != null) { + var isPremium = await _licensingService.VerifyUserPremiumAsync(user); newClaims.AddRange(new List { - new Claim("premium", user.Premium ? "true" : "false", ClaimValueTypes.Boolean), + new Claim("premium", isPremium ? "true" : "false", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.Email, user.Email), new Claim(JwtClaimTypes.EmailVerified, user.EmailVerified ? "true" : "false", ClaimValueTypes.Boolean), new Claim(_identityOptions.ClaimsIdentity.SecurityStampClaimType, user.SecurityStamp) diff --git a/src/Core/Models/Business/OrganizationLicense.cs b/src/Core/Models/Business/OrganizationLicense.cs index da1f5e41f..da067f988 100644 --- a/src/Core/Models/Business/OrganizationLicense.cs +++ b/src/Core/Models/Business/OrganizationLicense.cs @@ -167,7 +167,7 @@ namespace Bit.Core.Models.Business if(Version == 1) { return - organization.LicenseKey.Equals(LicenseKey, StringComparison.InvariantCultureIgnoreCase) && + organization.LicenseKey.Equals(LicenseKey) && organization.Enabled == Enabled && organization.PlanType == PlanType && organization.Seats == Seats && diff --git a/src/Core/Models/Business/UserLicense.cs b/src/Core/Models/Business/UserLicense.cs index f645dd133..4c0d6bad4 100644 --- a/src/Core/Models/Business/UserLicense.cs +++ b/src/Core/Models/Business/UserLicense.cs @@ -116,7 +116,7 @@ namespace Bit.Core.Models.Business if(Version == 1) { return - user.LicenseKey.Equals(LicenseKey, StringComparison.InvariantCultureIgnoreCase) && + user.LicenseKey.Equals(LicenseKey) && user.Premium == Premium && user.Email.Equals(Email, StringComparison.InvariantCultureIgnoreCase); } diff --git a/src/Core/Services/ILicensingService.cs b/src/Core/Services/ILicensingService.cs index c82650eda..bce8aa90d 100644 --- a/src/Core/Services/ILicensingService.cs +++ b/src/Core/Services/ILicensingService.cs @@ -1,12 +1,13 @@ using Bit.Core.Models.Business; using Bit.Core.Models.Table; +using System.Threading.Tasks; namespace Bit.Core.Services { public interface ILicensingService { bool VerifyOrganizationPlan(Organization organization); - bool VerifyUserPremium(User user); + Task VerifyUserPremiumAsync(User user); bool VerifyLicense(ILicense license); byte[] SignLicense(ILicense license); } diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 469c0edaf..38e17a40c 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -47,6 +47,7 @@ namespace Bit.Core.Services Task CancelPremiumAsync(User user, bool endOfPeriod = false); Task ReinstatePremiumAsync(User user); Task DisablePremiumAsync(Guid userId, DateTime? expirationDate); + Task DisablePremiumAsync(User user, DateTime? expirationDate); Task UpdatePremiumExpirationAsync(Guid userId, DateTime? expirationDate); } } diff --git a/src/Core/Services/Implementations/RsaLicensingService.cs b/src/Core/Services/Implementations/RsaLicensingService.cs index 3742a2a7f..04a977aeb 100644 --- a/src/Core/Services/Implementations/RsaLicensingService.cs +++ b/src/Core/Services/Implementations/RsaLicensingService.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.IO; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Threading.Tasks; namespace Bit.Core.Services { @@ -15,13 +16,17 @@ namespace Bit.Core.Services { private readonly X509Certificate2 _certificate; private readonly GlobalSettings _globalSettings; - private IDictionary _userLicenseCache; - private IDictionary _organizationLicenseCache; + private IDictionary _userCheckCache = new Dictionary(); + private IDictionary _organizationCheckCache = new Dictionary(); + private readonly IUserService _userService; public RsaLicensingService( + IUserService userService, IHostingEnvironment environment, GlobalSettings globalSettings) { + _userService = userService; + var certThumbprint = "‎207e64a231e8aa32aaf68a61037c075ebebd553f"; _globalSettings = globalSettings; _certificate = !_globalSettings.SelfHosted ? CoreHelpers.GetCertificate(certThumbprint) @@ -54,7 +59,7 @@ namespace Bit.Core.Services return license != null && license.VerifyData(organization) && license.VerifySignature(_certificate); } - public bool VerifyUserPremium(User user) + public async Task VerifyUserPremiumAsync(User user) { if(!_globalSettings.SelfHosted) { @@ -66,8 +71,33 @@ namespace Bit.Core.Services return false; } + // Only check once per day + var now = DateTime.UtcNow; + if(_userCheckCache.ContainsKey(user.Id)) + { + var lastCheck = _userCheckCache[user.Id]; + if(lastCheck < now && now - lastCheck < TimeSpan.FromDays(1)) + { + return user.Premium; + } + else + { + _userCheckCache[user.Id] = now; + } + } + else + { + _userCheckCache.Add(user.Id, now); + } + var license = ReadUserLicense(user); - return license != null && license.VerifyData(user) && license.VerifySignature(_certificate); + var licensedForPremium = license != null && license.VerifyData(user) && license.VerifySignature(_certificate); + if(!licensedForPremium) + { + await _userService.DisablePremiumAsync(user, license.Expires); + } + + return licensedForPremium; } public bool VerifyLicense(ILicense license) @@ -87,11 +117,6 @@ namespace Bit.Core.Services private UserLicense ReadUserLicense(User user) { - if(_userLicenseCache != null && _userLicenseCache.ContainsKey(user.LicenseKey)) - { - return _userLicenseCache[user.LicenseKey]; - } - var filePath = $"{_globalSettings.LicenseDirectory}/user/{user.Id}.json"; if(!File.Exists(filePath)) { @@ -99,22 +124,11 @@ namespace Bit.Core.Services } var data = File.ReadAllText(filePath, Encoding.UTF8); - var obj = JsonConvert.DeserializeObject(data); - if(_userLicenseCache == null) - { - _userLicenseCache = new Dictionary(); - } - _userLicenseCache.Add(obj.LicenseKey, obj); - return obj; + return JsonConvert.DeserializeObject(data); } private OrganizationLicense ReadOrganiztionLicense(Organization organization) { - if(_organizationLicenseCache != null && _organizationLicenseCache.ContainsKey(organization.LicenseKey)) - { - return _organizationLicenseCache[organization.LicenseKey]; - } - var filePath = $"{_globalSettings.LicenseDirectory}/organization/{organization.Id}.json"; if(!File.Exists(filePath)) { @@ -122,13 +136,7 @@ namespace Bit.Core.Services } var data = File.ReadAllText(filePath, Encoding.UTF8); - var obj = JsonConvert.DeserializeObject(data); - if(_organizationLicenseCache == null) - { - _organizationLicenseCache = new Dictionary(); - } - _organizationLicenseCache.Add(obj.LicenseKey, obj); - return obj; + return JsonConvert.DeserializeObject(data); } } } diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index a3c573a46..3ad473f0d 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -691,6 +691,11 @@ namespace Bit.Core.Services public async Task DisablePremiumAsync(Guid userId, DateTime? expirationDate) { var user = await _userRepository.GetByIdAsync(userId); + await DisablePremiumAsync(user, expirationDate); + } + + public async Task DisablePremiumAsync(User user, DateTime? expirationDate) + { if(user != null && user.Premium) { user.Premium = false; diff --git a/src/Core/Services/NoopImplementations/NoopLicensingService.cs b/src/Core/Services/NoopImplementations/NoopLicensingService.cs index e738dcfd5..9494ebb50 100644 --- a/src/Core/Services/NoopImplementations/NoopLicensingService.cs +++ b/src/Core/Services/NoopImplementations/NoopLicensingService.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Hosting; using System; using Bit.Core.Models.Business; +using System.Threading.Tasks; namespace Bit.Core.Services { @@ -27,9 +28,9 @@ namespace Bit.Core.Services return true; } - public bool VerifyUserPremium(User user) + public Task VerifyUserPremiumAsync(User user) { - return user.Premium; + return Task.FromResult(user.Premium); } public byte[] SignLicense(ILicense license)