From c41a1e093608503d91ea7db30bb0ec6f51e24631 Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 28 Aug 2018 16:23:58 -0400 Subject: [PATCH] CanAccessPremium checks instead of User.Premium --- src/Api/Controllers/TwoFactorController.cs | 4 +-- .../Identity/AuthenticatorTokenProvider.cs | 20 +++++++++----- src/Core/Identity/DuoWebTokenProvider.cs | 21 ++++++++++----- src/Core/Identity/U2fTokenProvider.cs | 18 +++++++++---- src/Core/Identity/YubicoOtpTokenProvider.cs | 21 ++++++++++----- .../ResourceOwnerPasswordValidator.cs | 16 +++++++---- src/Core/Models/Data/OrganizationAbility.cs | 2 ++ src/Core/Models/Table/User.cs | 15 ++++++++--- src/Core/Services/IUserService.cs | 1 + .../Services/Implementations/UserService.cs | 19 +++++++++++++ .../Organization_ReadAbilities.sql | 1 + .../2018-08-28_00_PremiumOrgAbilities.sql | 27 +++++++++++++++++++ 12 files changed, 130 insertions(+), 35 deletions(-) create mode 100644 util/Setup/DbScripts/2018-08-28_00_PremiumOrgAbilities.sql diff --git a/src/Api/Controllers/TwoFactorController.cs b/src/Api/Controllers/TwoFactorController.cs index bb234f50a7..be005a7f35 100644 --- a/src/Api/Controllers/TwoFactorController.cs +++ b/src/Api/Controllers/TwoFactorController.cs @@ -342,9 +342,9 @@ namespace Bit.Api.Controllers throw new BadRequestException("MasterPasswordHash", "Invalid password."); } - if(premium && !user.Premium) + if(premium && !(await _userService.CanAccessPremium(user))) { - throw new BadRequestException("Premium membership required."); + throw new BadRequestException("Premium status is required."); } return user; diff --git a/src/Core/Identity/AuthenticatorTokenProvider.cs b/src/Core/Identity/AuthenticatorTokenProvider.cs index d60129c887..ddde7c23f9 100644 --- a/src/Core/Identity/AuthenticatorTokenProvider.cs +++ b/src/Core/Identity/AuthenticatorTokenProvider.cs @@ -4,19 +4,27 @@ using Microsoft.AspNetCore.Identity; using Bit.Core.Models.Table; using Bit.Core.Enums; using OtpNet; +using Bit.Core.Services; namespace Bit.Core.Identity { public class AuthenticatorTokenProvider : IUserTwoFactorTokenProvider { - public Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) + private readonly IUserService _userService; + + public AuthenticatorTokenProvider(IUserService userService) + { + _userService = userService; + } + + public async Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) { var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator); - - var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.Authenticator) - && !string.IsNullOrWhiteSpace((string)provider.MetaData["Key"]); - - return Task.FromResult(canGenerate); + if(string.IsNullOrWhiteSpace((string)provider.MetaData["Key"])) + { + return false; + } + return await user.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Authenticator, _userService); } public Task GenerateAsync(string purpose, UserManager manager, User user) diff --git a/src/Core/Identity/DuoWebTokenProvider.cs b/src/Core/Identity/DuoWebTokenProvider.cs index de5d58f4be..bcda05be94 100644 --- a/src/Core/Identity/DuoWebTokenProvider.cs +++ b/src/Core/Identity/DuoWebTokenProvider.cs @@ -4,28 +4,37 @@ using Bit.Core.Models.Table; using Bit.Core.Enums; using Bit.Core.Utilities.Duo; using Bit.Core.Models; +using Bit.Core.Services; namespace Bit.Core.Identity { public class DuoWebTokenProvider : IUserTwoFactorTokenProvider { + private readonly IUserService _userService; private readonly GlobalSettings _globalSettings; - public DuoWebTokenProvider(GlobalSettings globalSettings) + public DuoWebTokenProvider( + IUserService userService, + GlobalSettings globalSettings) { + _userService = userService; _globalSettings = globalSettings; } - public Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) + public async Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) { - if(!user.Premium) + if(!(await _userService.CanAccessPremium(user))) { - return Task.FromResult(false); + return false; } var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); - var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.Duo) && HasProperMetaData(provider); - return Task.FromResult(canGenerate); + if(!HasProperMetaData(provider)) + { + return false; + } + + return await user.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Duo, _userService); } public Task GenerateAsync(string purpose, UserManager manager, User user) diff --git a/src/Core/Identity/U2fTokenProvider.cs b/src/Core/Identity/U2fTokenProvider.cs index 6f45682ac1..737546c158 100644 --- a/src/Core/Identity/U2fTokenProvider.cs +++ b/src/Core/Identity/U2fTokenProvider.cs @@ -11,32 +11,40 @@ using U2fLib = U2F.Core.Crypto.U2F; using U2F.Core.Models; using U2F.Core.Exceptions; using System; +using Bit.Core.Services; namespace Bit.Core.Identity { public class U2fTokenProvider : IUserTwoFactorTokenProvider { + private readonly IUserService _userService; private readonly IU2fRepository _u2fRepository; private readonly GlobalSettings _globalSettings; public U2fTokenProvider( + IUserService userService, IU2fRepository u2fRepository, GlobalSettings globalSettings) { + _userService = userService; _u2fRepository = u2fRepository; _globalSettings = globalSettings; } - public Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) + public async Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) { - if(!user.Premium) + if(!(await _userService.CanAccessPremium(user))) { - return Task.FromResult(false); + return false; } var provider = user.GetTwoFactorProvider(TwoFactorProviderType.U2f); - var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.U2f) && HasProperMetaData(provider); - return Task.FromResult(canGenerate); + if(!HasProperMetaData(provider)) + { + return false; + } + + return await user.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.U2f, _userService); } public async Task GenerateAsync(string purpose, UserManager manager, User user) diff --git a/src/Core/Identity/YubicoOtpTokenProvider.cs b/src/Core/Identity/YubicoOtpTokenProvider.cs index 286474e9c5..1fe7fb0a9c 100644 --- a/src/Core/Identity/YubicoOtpTokenProvider.cs +++ b/src/Core/Identity/YubicoOtpTokenProvider.cs @@ -4,30 +4,37 @@ using Bit.Core.Models.Table; using Bit.Core.Enums; using YubicoDotNetClient; using System.Linq; +using Bit.Core.Services; namespace Bit.Core.Identity { public class YubicoOtpTokenProvider : IUserTwoFactorTokenProvider { + private readonly IUserService _userService; private readonly GlobalSettings _globalSettings; - public YubicoOtpTokenProvider(GlobalSettings globalSettings) + public YubicoOtpTokenProvider( + IUserService userService, + GlobalSettings globalSettings) { + _userService = userService; _globalSettings = globalSettings; } - public Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) + public async Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) { - if(!user.Premium) + if(!(await _userService.CanAccessPremium(user))) { - return Task.FromResult(false); + return false; } var provider = user.GetTwoFactorProvider(TwoFactorProviderType.YubiKey); - var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.YubiKey) - && (provider?.MetaData.Values.Any(v => !string.IsNullOrWhiteSpace((string)v)) ?? false); + if(!provider?.MetaData.Values.Any(v => !string.IsNullOrWhiteSpace((string)v)) ?? true) + { + return false; + } - return Task.FromResult(canGenerate); + return await user.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.YubiKey, _userService); } public Task GenerateAsync(string purpose, UserManager manager, User user) diff --git a/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs b/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs index 7cf6887b01..25a070c02d 100644 --- a/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs +++ b/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs @@ -162,8 +162,13 @@ namespace Bit.Core.IdentityServer if(user.GetTwoFactorProviders() != null) { - enabledProviders.AddRange( - user.GetTwoFactorProviders().Where(p => user.TwoFactorProviderIsEnabled(p.Key))); + foreach(var p in user.GetTwoFactorProviders()) + { + if(await user.TwoFactorProviderIsEnabledAsync(p.Key, _userService)) + { + enabledProviders.Add(p); + } + } } if(!enabledProviders.Any()) @@ -273,13 +278,14 @@ namespace Bit.Core.IdentityServer case TwoFactorProviderType.YubiKey: case TwoFactorProviderType.U2f: case TwoFactorProviderType.Remember: - if(type != TwoFactorProviderType.Remember && !user.TwoFactorProviderIsEnabled(type)) + if(type != TwoFactorProviderType.Remember && + !(await user.TwoFactorProviderIsEnabledAsync(type, _userService))) { return false; } return await _userManager.VerifyTwoFactorTokenAsync(user, type.ToString(), token); case TwoFactorProviderType.Email: - if(!user.TwoFactorProviderIsEnabled(type)) + if(!(await user.TwoFactorProviderIsEnabledAsync(type, _userService))) { return false; } @@ -305,7 +311,7 @@ namespace Bit.Core.IdentityServer case TwoFactorProviderType.U2f: case TwoFactorProviderType.Email: case TwoFactorProviderType.YubiKey: - if(!user.TwoFactorProviderIsEnabled(type)) + if(!(await user.TwoFactorProviderIsEnabledAsync(type, _userService))) { return null; } diff --git a/src/Core/Models/Data/OrganizationAbility.cs b/src/Core/Models/Data/OrganizationAbility.cs index 36d50b8eea..4e8bc76896 100644 --- a/src/Core/Models/Data/OrganizationAbility.cs +++ b/src/Core/Models/Data/OrganizationAbility.cs @@ -14,6 +14,7 @@ namespace Bit.Core.Models.Data Use2fa = organization.Use2fa; Using2fa = organization.Use2fa && organization.TwoFactorProviders != null && organization.TwoFactorProviders != "{}"; + UsersGetPremium = organization.UsersGetPremium; Enabled = organization.Enabled; } @@ -21,6 +22,7 @@ namespace Bit.Core.Models.Data public bool UseEvents { get; set; } public bool Use2fa { get; set; } public bool Using2fa { get; set; } + public bool UsersGetPremium { get; set; } public bool Enabled { get; set; } } } diff --git a/src/Core/Models/Table/User.cs b/src/Core/Models/Table/User.cs index 31fedb4501..a976aa0e99 100644 --- a/src/Core/Models/Table/User.cs +++ b/src/Core/Models/Table/User.cs @@ -7,6 +7,7 @@ using System.Linq; using Bit.Core.Services; using Bit.Core.Exceptions; using Microsoft.AspNetCore.Identity; +using System.Threading.Tasks; namespace Bit.Core.Models.Table { @@ -92,15 +93,21 @@ namespace Bit.Core.Models.Table _twoFactorProviders = providers; } - public bool TwoFactorProviderIsEnabled(TwoFactorProviderType provider) + public async Task TwoFactorProviderIsEnabledAsync(TwoFactorProviderType provider, + IUserService userService) { var providers = GetTwoFactorProviders(); - if(providers == null || !providers.ContainsKey(provider)) + if(providers == null || !providers.ContainsKey(provider) || !providers[provider].Enabled) { return false; } - return providers[provider].Enabled && (Premium || !TwoFactorProvider.RequiresPremium(provider)); + if(!TwoFactorProvider.RequiresPremium(provider)) + { + return true; + } + + return await userService.CanAccessPremium(this); } public bool TwoFactorIsEnabled() @@ -111,7 +118,7 @@ namespace Bit.Core.Models.Table return false; } - return providers.Any(p => (p.Value?.Enabled ?? false) && + return providers.Any(p => (p.Value?.Enabled ?? false) && (Premium || !TwoFactorProvider.RequiresPremium(p.Key))); } diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 8eb31eeb95..b0bd6fe47d 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -53,5 +53,6 @@ namespace Bit.Core.Services Task UpdatePremiumExpirationAsync(Guid userId, DateTime? expirationDate); Task GenerateLicenseAsync(User user, BillingInfo billingInfo = null); Task CheckPasswordAsync(User user, string password); + Task CanAccessPremium(User user); } } diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 16abfe579f..a2ce5d91f9 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -39,6 +39,7 @@ namespace Bit.Core.Services private readonly IEnumerable> _passwordValidators; private readonly ILicensingService _licenseService; private readonly IEventService _eventService; + private readonly IApplicationCacheService _applicationCacheService; private readonly IDataProtector _organizationServiceDataProtector; private readonly CurrentContext _currentContext; private readonly GlobalSettings _globalSettings; @@ -61,6 +62,7 @@ namespace Bit.Core.Services ILogger> logger, ILicensingService licenseService, IEventService eventService, + IApplicationCacheService applicationCacheService, IDataProtectionProvider dataProtectionProvider, CurrentContext currentContext, GlobalSettings globalSettings) @@ -87,6 +89,7 @@ namespace Bit.Core.Services _passwordValidators = passwordValidators; _licenseService = licenseService; _eventService = eventService; + _applicationCacheService = applicationCacheService; _organizationServiceDataProtector = dataProtectionProvider.CreateProtector( "OrganizationServiceDataProtector"); _currentContext = currentContext; @@ -822,6 +825,22 @@ namespace Bit.Core.Services return success; } + public async Task CanAccessPremium(User user) + { + if(user.Premium) + { + return true; + } + if(!_currentContext?.Organizations?.Any() ?? true) + { + return false; + } + + var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync(); + return _currentContext.Organizations.Any(o => orgAbilities.ContainsKey(o.Id) && + orgAbilities[o.Id].UsersGetPremium); + } + private async Task UpdatePasswordHash(User user, string newPassword, bool validatePassword = true, bool refreshStamp = true) { diff --git a/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql b/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql index e2cefcb038..1eea2777c9 100644 --- a/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql +++ b/src/Sql/dbo/Stored Procedures/Organization_ReadAbilities.sql @@ -13,6 +13,7 @@ BEGIN ELSE 0 END AS [Using2fa], + [UsersGetPremium], [Enabled] FROM [dbo].[Organization] diff --git a/util/Setup/DbScripts/2018-08-28_00_PremiumOrgAbilities.sql b/util/Setup/DbScripts/2018-08-28_00_PremiumOrgAbilities.sql new file mode 100644 index 0000000000..e08ed0fa34 --- /dev/null +++ b/util/Setup/DbScripts/2018-08-28_00_PremiumOrgAbilities.sql @@ -0,0 +1,27 @@ +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], + CASE + WHEN [Use2fa] = 1 AND [TwoFactorProviders] IS NOT NULL AND [TwoFactorProviders] != '{}' THEN + 1 + ELSE + 0 + END AS [Using2fa], + [UsersGetPremium], + [Enabled] + FROM + [dbo].[Organization] +END +GO