1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 21:18:13 -05:00

More CanAccessPremium checks

This commit is contained in:
Kyle Spearrin 2018-08-28 17:40:08 -04:00
parent c41a1e0936
commit cf73b168ee
16 changed files with 90 additions and 48 deletions

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq;
using Bit.Core; using Bit.Core;
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using Bit.Core.Utilities; using Bit.Core.Utilities;

View File

@ -47,6 +47,7 @@ namespace Bit.Admin
services.AddScoped<CurrentContext>(); services.AddScoped<CurrentContext>();
// Identity // Identity
services.AddBasicCustomIdentityServices(globalSettings);
services.AddPasswordlessIdentityServices<ReadOnlyEnvIdentityUserStore>(globalSettings); services.AddPasswordlessIdentityServices<ReadOnlyEnvIdentityUserStore>(globalSettings);
if(globalSettings.SelfHosted) if(globalSettings.SelfHosted)
{ {

View File

@ -1,4 +1,5 @@
@model UsersModel @model UsersModel
@inject Bit.Core.Services.IUserService userService
@{ @{
ViewData["Title"] = "Users"; ViewData["Title"] = "Users";
} }
@ -68,7 +69,7 @@
{ {
<i class="fa fa-times-circle-o fa-lg fa-fw text-muted" title="Email Not Verified"></i> <i class="fa fa-times-circle-o fa-lg fa-fw text-muted" title="Email Not Verified"></i>
} }
@if(user.TwoFactorIsEnabled()) @if(await user.TwoFactorIsEnabledAsync(userService))
{ {
<i class="fa fa-lock fa-lg fa-fw" title="2FA Enabled"></i> <i class="fa fa-lock fa-lg fa-fw" title="2FA Enabled"></i>
} }

View File

@ -1,4 +1,5 @@
@model UserViewModel @model UserViewModel
@inject Bit.Core.Services.IUserService userService
<dl class="row"> <dl class="row">
<dt class="col-sm-4 col-lg-3">Id</dt> <dt class="col-sm-4 col-lg-3">Id</dt>
<dd class="col-sm-8 col-lg-9"><code>@Model.User.Id</code></dd> <dd class="col-sm-8 col-lg-9"><code>@Model.User.Id</code></dd>
@ -13,7 +14,7 @@
<dd class="col-sm-8 col-lg-9">@(Model.User.EmailVerified ? "Yes" : "No")</dd> <dd class="col-sm-8 col-lg-9">@(Model.User.EmailVerified ? "Yes" : "No")</dd>
<dt class="col-sm-4 col-lg-3">Using 2FA</dt> <dt class="col-sm-4 col-lg-3">Using 2FA</dt>
<dd class="col-sm-8 col-lg-9">@(Model.User.TwoFactorIsEnabled() ? "Yes" : "No")</dd> <dd class="col-sm-8 col-lg-9">@(await Model.User.TwoFactorIsEnabledAsync(userService) ? "Yes" : "No")</dd>
<dt class="col-sm-4 col-lg-3">Items</dt> <dt class="col-sm-4 col-lg-3">Items</dt>
<dd class="col-sm-8 col-lg-9">@Model.CipherCount</dd> <dd class="col-sm-8 col-lg-9">@Model.CipherCount</dd>

View File

@ -282,7 +282,8 @@ namespace Bit.Api.Controllers
var organizationUserDetails = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id, var organizationUserDetails = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id,
OrganizationUserStatusType.Confirmed); OrganizationUserStatusType.Confirmed);
var response = new ProfileResponseModel(user, organizationUserDetails); var response = new ProfileResponseModel(user, organizationUserDetails,
await user.TwoFactorIsEnabledAsync(_userService));
return response; return response;
} }
@ -307,7 +308,7 @@ namespace Bit.Api.Controllers
} }
await _userService.SaveUserAsync(model.ToUser(user)); await _userService.SaveUserAsync(model.ToUser(user));
var response = new ProfileResponseModel(user, null); var response = new ProfileResponseModel(user, null, await user.TwoFactorIsEnabledAsync(_userService));
return response; return response;
} }
@ -437,7 +438,7 @@ namespace Bit.Api.Controllers
await _userService.SignUpPremiumAsync(user, model.PaymentToken, await _userService.SignUpPremiumAsync(user, model.PaymentToken,
model.AdditionalStorageGb.GetValueOrDefault(0), license); model.AdditionalStorageGb.GetValueOrDefault(0), license);
return new ProfileResponseModel(user, null); return new ProfileResponseModel(user, null, await user.TwoFactorIsEnabledAsync(_userService));
} }
[HttpGet("billing")] [HttpGet("billing")]

View File

@ -69,8 +69,9 @@ namespace Bit.Api.Controllers
collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key); collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);
} }
var response = new SyncResponseModel(_globalSettings, user, organizationUserDetails, folders, var userTwoFactorEnabled = await user.TwoFactorIsEnabledAsync(_userService);
collections, ciphers, collectionCiphersGroupDict, excludeDomains); var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, organizationUserDetails,
folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains);
return response; return response;
} }
} }

View File

@ -37,41 +37,41 @@ namespace Bit.Core.Identity
return await user.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Duo, _userService); return await user.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Duo, _userService);
} }
public Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user) public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
{ {
if(!user.Premium) if(!(await _userService.CanAccessPremium(user)))
{ {
return Task.FromResult<string>(null); return null;
} }
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo);
if(!HasProperMetaData(provider)) if(!HasProperMetaData(provider))
{ {
return Task.FromResult<string>(null); return null;
} }
var signatureRequest = DuoWeb.SignRequest((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"], var signatureRequest = DuoWeb.SignRequest((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"],
_globalSettings.Duo.AKey, user.Email); _globalSettings.Duo.AKey, user.Email);
return Task.FromResult(signatureRequest); return signatureRequest;
} }
public Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user) public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
{ {
if(!user.Premium) if(!(await _userService.CanAccessPremium(user)))
{ {
return Task.FromResult(false); return false;
} }
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo);
if(!HasProperMetaData(provider)) if(!HasProperMetaData(provider))
{ {
return Task.FromResult(false); return false;
} }
var response = DuoWeb.VerifyResponse((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"], var response = DuoWeb.VerifyResponse((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"],
_globalSettings.Duo.AKey, token); _globalSettings.Duo.AKey, token);
return Task.FromResult(response == user.Email); return response == user.Email;
} }
private bool HasProperMetaData(TwoFactorProvider provider) private bool HasProperMetaData(TwoFactorProvider provider)

View File

@ -3,15 +3,20 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services;
namespace Bit.Core.Identity namespace Bit.Core.Identity
{ {
public class ReadOnlyDatabaseIdentityUserStore : ReadOnlyIdentityUserStore public class ReadOnlyDatabaseIdentityUserStore : ReadOnlyIdentityUserStore
{ {
private readonly IUserService _userService;
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
public ReadOnlyDatabaseIdentityUserStore(IUserRepository userRepository) public ReadOnlyDatabaseIdentityUserStore(
IUserService userService,
IUserRepository userRepository)
{ {
_userService = userService;
_userRepository = userRepository; _userRepository = userRepository;
} }
@ -19,7 +24,7 @@ namespace Bit.Core.Identity
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
var user = await _userRepository.GetByEmailAsync(normalizedEmail); var user = await _userRepository.GetByEmailAsync(normalizedEmail);
return user?.ToIdentityUser(); return user?.ToIdentityUser(await user.TwoFactorIsEnabledAsync(_userService));
} }
public override async Task<IdentityUser> FindByIdAsync(string userId, public override async Task<IdentityUser> FindByIdAsync(string userId,
@ -31,7 +36,7 @@ namespace Bit.Core.Identity
} }
var user = await _userRepository.GetByIdAsync(userIdGuid); var user = await _userRepository.GetByIdAsync(userIdGuid);
return user?.ToIdentityUser(); return user?.ToIdentityUser(await user.TwoFactorIsEnabledAsync(_userService));
} }
} }
} }

View File

@ -49,7 +49,7 @@ namespace Bit.Core.Identity
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user) public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
{ {
if(!user.Premium) if(!(await _userService.CanAccessPremium(user)))
{ {
return null; return null;
} }
@ -108,7 +108,7 @@ namespace Bit.Core.Identity
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user) public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
{ {
if(!user.Premium || string.IsNullOrWhiteSpace(token)) if(!(await _userService.CanAccessPremium(user)) || string.IsNullOrWhiteSpace(token))
{ {
return false; return false;
} }

View File

@ -4,6 +4,8 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Bit.Core.Models.Table; using Bit.Core.Models.Table;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.Identity namespace Bit.Core.Identity
{ {
@ -14,13 +16,16 @@ namespace Bit.Core.Identity
IUserTwoFactorStore<User>, IUserTwoFactorStore<User>,
IUserSecurityStampStore<User> IUserSecurityStampStore<User>
{ {
private readonly IServiceProvider _serviceProvider;
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
private readonly CurrentContext _currentContext; private readonly CurrentContext _currentContext;
public UserStore( public UserStore(
IServiceProvider serviceProvider,
IUserRepository userRepository, IUserRepository userRepository,
CurrentContext currentContext) CurrentContext currentContext)
{ {
_serviceProvider = serviceProvider;
_userRepository = userRepository; _userRepository = userRepository;
_currentContext = currentContext; _currentContext = currentContext;
} }
@ -162,9 +167,9 @@ namespace Bit.Core.Identity
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task<bool> GetTwoFactorEnabledAsync(User user, CancellationToken cancellationToken) public async Task<bool> GetTwoFactorEnabledAsync(User user, CancellationToken cancellationToken)
{ {
return Task.FromResult(user.TwoFactorIsEnabled()); return await user.TwoFactorIsEnabledAsync(_serviceProvider.GetRequiredService<IUserService>());
} }
public Task SetSecurityStampAsync(User user, string stamp, CancellationToken cancellationToken) public Task SetSecurityStampAsync(User user, string stamp, CancellationToken cancellationToken)

View File

@ -44,7 +44,7 @@ namespace Bit.Core.Identity
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user) public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
{ {
if(!user.Premium) if(!(await _userService.CanAccessPremium(user)))
{ {
return false; return false;
} }

View File

@ -3,12 +3,14 @@ using Bit.Core.Models.Table;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Services;
namespace Bit.Core.Models.Api namespace Bit.Core.Models.Api
{ {
public class ProfileResponseModel : ResponseModel public class ProfileResponseModel : ResponseModel
{ {
public ProfileResponseModel(User user, IEnumerable<OrganizationUserOrganizationDetails> organizationsUserDetails) public ProfileResponseModel(User user,
IEnumerable<OrganizationUserOrganizationDetails> organizationsUserDetails, bool twoFactorEnabled)
: base("profile") : base("profile")
{ {
if(user == null) if(user == null)
@ -23,7 +25,7 @@ namespace Bit.Core.Models.Api
Premium = user.Premium; Premium = user.Premium;
MasterPasswordHint = string.IsNullOrWhiteSpace(user.MasterPasswordHint) ? null : user.MasterPasswordHint; MasterPasswordHint = string.IsNullOrWhiteSpace(user.MasterPasswordHint) ? null : user.MasterPasswordHint;
Culture = user.Culture; Culture = user.Culture;
TwoFactorEnabled = user.TwoFactorIsEnabled(); TwoFactorEnabled = twoFactorEnabled;
Key = user.Key; Key = user.Key;
PrivateKey = user.PrivateKey; PrivateKey = user.PrivateKey;
SecurityStamp = user.SecurityStamp; SecurityStamp = user.SecurityStamp;

View File

@ -12,6 +12,7 @@ namespace Bit.Core.Models.Api
public SyncResponseModel( public SyncResponseModel(
GlobalSettings globalSettings, GlobalSettings globalSettings,
User user, User user,
bool userTwoFactorEnabled,
IEnumerable<OrganizationUserOrganizationDetails> organizationUserDetails, IEnumerable<OrganizationUserOrganizationDetails> organizationUserDetails,
IEnumerable<Folder> folders, IEnumerable<Folder> folders,
IEnumerable<CollectionDetails> collections, IEnumerable<CollectionDetails> collections,
@ -20,7 +21,7 @@ namespace Bit.Core.Models.Api
bool excludeDomains) bool excludeDomains)
: base("sync") : base("sync")
{ {
Profile = new ProfileResponseModel(user, organizationUserDetails); Profile = new ProfileResponseModel(user, organizationUserDetails, userTwoFactorEnabled);
Folders = folders.Select(f => new FolderResponseModel(f)); Folders = folders.Select(f => new FolderResponseModel(f));
Ciphers = ciphers.Select(c => new CipherDetailsResponseModel(c, globalSettings, collectionCiphersDict)); Ciphers = ciphers.Select(c => new CipherDetailsResponseModel(c, globalSettings, collectionCiphersDict));
Collections = collections?.Select( Collections = collections?.Select(

View File

@ -3,7 +3,6 @@ using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using System.Collections.Generic; using System.Collections.Generic;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Linq;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
@ -110,7 +109,7 @@ namespace Bit.Core.Models.Table
return await userService.CanAccessPremium(this); return await userService.CanAccessPremium(this);
} }
public bool TwoFactorIsEnabled() public async Task<bool> TwoFactorIsEnabledAsync(IUserService userService)
{ {
var providers = GetTwoFactorProviders(); var providers = GetTwoFactorProviders();
if(providers == null) if(providers == null)
@ -118,8 +117,21 @@ namespace Bit.Core.Models.Table
return false; return false;
} }
return providers.Any(p => (p.Value?.Enabled ?? false) && foreach(var p in providers)
(Premium || !TwoFactorProvider.RequiresPremium(p.Key))); {
if(p.Value?.Enabled ?? false)
{
if(!TwoFactorProvider.RequiresPremium(p.Key))
{
return true;
}
if(await userService.CanAccessPremium(this))
{
return true;
}
}
}
return false;
} }
public TwoFactorProvider GetTwoFactorProvider(TwoFactorProviderType provider) public TwoFactorProvider GetTwoFactorProvider(TwoFactorProviderType provider)
@ -177,7 +189,7 @@ namespace Bit.Core.Models.Table
return paymentService; return paymentService;
} }
public IdentityUser ToIdentityUser() public IdentityUser ToIdentityUser(bool twoFactorEnabled)
{ {
return new IdentityUser return new IdentityUser
{ {
@ -187,7 +199,7 @@ namespace Bit.Core.Models.Table
EmailConfirmed = EmailVerified, EmailConfirmed = EmailVerified,
UserName = Email, UserName = Email,
NormalizedUserName = Email, NormalizedUserName = Email,
TwoFactorEnabled = TwoFactorIsEnabled(), TwoFactorEnabled = twoFactorEnabled,
SecurityStamp = SecurityStamp SecurityStamp = SecurityStamp
}; };
} }

View File

@ -24,6 +24,7 @@ namespace Bit.Core.Services
private readonly IPushNotificationService _pushService; private readonly IPushNotificationService _pushService;
private readonly IAttachmentStorageService _attachmentStorageService; private readonly IAttachmentStorageService _attachmentStorageService;
private readonly IEventService _eventService; private readonly IEventService _eventService;
private readonly IUserService _userService;
public CipherService( public CipherService(
ICipherRepository cipherRepository, ICipherRepository cipherRepository,
@ -35,7 +36,8 @@ namespace Bit.Core.Services
ICollectionCipherRepository collectionCipherRepository, ICollectionCipherRepository collectionCipherRepository,
IPushNotificationService pushService, IPushNotificationService pushService,
IAttachmentStorageService attachmentStorageService, IAttachmentStorageService attachmentStorageService,
IEventService eventService) IEventService eventService,
IUserService userService)
{ {
_cipherRepository = cipherRepository; _cipherRepository = cipherRepository;
_folderRepository = folderRepository; _folderRepository = folderRepository;
@ -47,6 +49,7 @@ namespace Bit.Core.Services
_pushService = pushService; _pushService = pushService;
_attachmentStorageService = attachmentStorageService; _attachmentStorageService = attachmentStorageService;
_eventService = eventService; _eventService = eventService;
_userService = userService;
} }
public async Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false) public async Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false)
@ -125,9 +128,9 @@ namespace Bit.Core.Services
if(cipher.UserId.HasValue) if(cipher.UserId.HasValue)
{ {
var user = await _userRepository.GetByIdAsync(cipher.UserId.Value); var user = await _userRepository.GetByIdAsync(cipher.UserId.Value);
if(!user.Premium) if(!(await _userService.CanAccessPremium(user)))
{ {
throw new BadRequestException("You must be a premium user to use attachments."); throw new BadRequestException("You must have premium status to use attachments.");
} }
storageBytesRemaining = user.StorageBytesRemaining(); storageBytesRemaining = user.StorageBytesRemaining();

View File

@ -153,16 +153,13 @@ namespace Bit.Core.Utilities
public static IdentityBuilder AddCustomIdentityServices( public static IdentityBuilder AddCustomIdentityServices(
this IServiceCollection services, GlobalSettings globalSettings) this IServiceCollection services, GlobalSettings globalSettings)
{ {
services.AddTransient<ILookupNormalizer, LowerInvariantLookupNormalizer>();
services.AddSingleton<IOrganizationDuoWebTokenProvider, OrganizationDuoWebTokenProvider>(); services.AddSingleton<IOrganizationDuoWebTokenProvider, OrganizationDuoWebTokenProvider>();
services.Configure<PasswordHasherOptions>(options => options.IterationCount = 75000);
services.Configure<TwoFactorRememberTokenProviderOptions>(options => services.Configure<TwoFactorRememberTokenProviderOptions>(options =>
{ {
options.TokenLifespan = TimeSpan.FromDays(30); options.TokenLifespan = TimeSpan.FromDays(30);
}); });
var identityBuilder = services.AddIdentityWithoutCookieAuth<User, Role>(options => var identityBuilder = services.AddBasicCustomIdentityServices(globalSettings, options =>
{ {
options.User = new UserOptions options.User = new UserOptions
{ {
@ -187,9 +184,6 @@ namespace Bit.Core.Utilities
}); });
identityBuilder identityBuilder
.AddUserStore<UserStore>()
.AddRoleStore<RoleStore>()
.AddTokenProvider<DataProtectorTokenProvider<User>>(TokenOptions.DefaultProvider)
.AddTokenProvider<AuthenticatorTokenProvider>(TwoFactorProviderType.Authenticator.ToString()) .AddTokenProvider<AuthenticatorTokenProvider>(TwoFactorProviderType.Authenticator.ToString())
.AddTokenProvider<YubicoOtpTokenProvider>(TwoFactorProviderType.YubiKey.ToString()) .AddTokenProvider<YubicoOtpTokenProvider>(TwoFactorProviderType.YubiKey.ToString())
.AddTokenProvider<DuoWebTokenProvider>(TwoFactorProviderType.Duo.ToString()) .AddTokenProvider<DuoWebTokenProvider>(TwoFactorProviderType.Duo.ToString())
@ -200,11 +194,27 @@ namespace Bit.Core.Utilities
return identityBuilder; return identityBuilder;
} }
public static IdentityBuilder AddPasswordlessIdentityServices<TUserStore>( public static IdentityBuilder AddBasicCustomIdentityServices(
this IServiceCollection services, GlobalSettings globalSettings) where TUserStore : class this IServiceCollection services, GlobalSettings globalSettings,
Action<IdentityOptions> setAction = null)
{ {
services.AddTransient<ILookupNormalizer, LowerInvariantLookupNormalizer>(); services.TryAddTransient<ILookupNormalizer, LowerInvariantLookupNormalizer>();
services.Configure<PasswordHasherOptions>(options => options.IterationCount = 75000);
var identityBuilder = services.AddIdentityWithoutCookieAuth<User, Role>(setAction);
identityBuilder
.AddUserStore<UserStore>()
.AddRoleStore<RoleStore>()
.AddTokenProvider<DataProtectorTokenProvider<User>>(TokenOptions.DefaultProvider);
return identityBuilder;
}
public static IdentityBuilder AddPasswordlessIdentityServices<TUserStore>(
this IServiceCollection services, GlobalSettings globalSettings) where TUserStore : class
{
services.TryAddTransient<ILookupNormalizer, LowerInvariantLookupNormalizer>();
services.Configure<DataProtectionTokenProviderOptions>(options => services.Configure<DataProtectionTokenProviderOptions>(options =>
{ {
options.TokenLifespan = TimeSpan.FromMinutes(15); options.TokenLifespan = TimeSpan.FromMinutes(15);