mirror of
https://github.com/bitwarden/server.git
synced 2025-04-05 05:00:19 -05:00
More CanAccessPremium checks
This commit is contained in:
parent
c41a1e0936
commit
cf73b168ee
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Utilities;
|
||||
|
@ -47,6 +47,7 @@ namespace Bit.Admin
|
||||
services.AddScoped<CurrentContext>();
|
||||
|
||||
// Identity
|
||||
services.AddBasicCustomIdentityServices(globalSettings);
|
||||
services.AddPasswordlessIdentityServices<ReadOnlyEnvIdentityUserStore>(globalSettings);
|
||||
if(globalSettings.SelfHosted)
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
@model UsersModel
|
||||
@inject Bit.Core.Services.IUserService userService
|
||||
@{
|
||||
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>
|
||||
}
|
||||
@if(user.TwoFactorIsEnabled())
|
||||
@if(await user.TwoFactorIsEnabledAsync(userService))
|
||||
{
|
||||
<i class="fa fa-lock fa-lg fa-fw" title="2FA Enabled"></i>
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
@model UserViewModel
|
||||
@inject Bit.Core.Services.IUserService userService
|
||||
<dl class="row">
|
||||
<dt class="col-sm-4 col-lg-3">Id</dt>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<dd class="col-sm-8 col-lg-9">@Model.CipherCount</dd>
|
||||
|
@ -282,7 +282,8 @@ namespace Bit.Api.Controllers
|
||||
|
||||
var organizationUserDetails = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id,
|
||||
OrganizationUserStatusType.Confirmed);
|
||||
var response = new ProfileResponseModel(user, organizationUserDetails);
|
||||
var response = new ProfileResponseModel(user, organizationUserDetails,
|
||||
await user.TwoFactorIsEnabledAsync(_userService));
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -307,7 +308,7 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
|
||||
await _userService.SaveUserAsync(model.ToUser(user));
|
||||
var response = new ProfileResponseModel(user, null);
|
||||
var response = new ProfileResponseModel(user, null, await user.TwoFactorIsEnabledAsync(_userService));
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -437,7 +438,7 @@ namespace Bit.Api.Controllers
|
||||
|
||||
await _userService.SignUpPremiumAsync(user, model.PaymentToken,
|
||||
model.AdditionalStorageGb.GetValueOrDefault(0), license);
|
||||
return new ProfileResponseModel(user, null);
|
||||
return new ProfileResponseModel(user, null, await user.TwoFactorIsEnabledAsync(_userService));
|
||||
}
|
||||
|
||||
[HttpGet("billing")]
|
||||
|
@ -69,8 +69,9 @@ namespace Bit.Api.Controllers
|
||||
collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);
|
||||
}
|
||||
|
||||
var response = new SyncResponseModel(_globalSettings, user, organizationUserDetails, folders,
|
||||
collections, ciphers, collectionCiphersGroupDict, excludeDomains);
|
||||
var userTwoFactorEnabled = await user.TwoFactorIsEnabledAsync(_userService);
|
||||
var response = new SyncResponseModel(_globalSettings, user, userTwoFactorEnabled, organizationUserDetails,
|
||||
folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
@ -37,41 +37,41 @@ namespace Bit.Core.Identity
|
||||
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);
|
||||
if(!HasProperMetaData(provider))
|
||||
{
|
||||
return Task.FromResult<string>(null);
|
||||
return null;
|
||||
}
|
||||
|
||||
var signatureRequest = DuoWeb.SignRequest((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"],
|
||||
_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);
|
||||
if(!HasProperMetaData(provider))
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
var response = DuoWeb.VerifyResponse((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"],
|
||||
_globalSettings.Duo.AKey, token);
|
||||
|
||||
return Task.FromResult(response == user.Email);
|
||||
return response == user.Email;
|
||||
}
|
||||
|
||||
private bool HasProperMetaData(TwoFactorProvider provider)
|
||||
|
@ -3,15 +3,20 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.Identity
|
||||
{
|
||||
public class ReadOnlyDatabaseIdentityUserStore : ReadOnlyIdentityUserStore
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public ReadOnlyDatabaseIdentityUserStore(IUserRepository userRepository)
|
||||
public ReadOnlyDatabaseIdentityUserStore(
|
||||
IUserService userService,
|
||||
IUserRepository userRepository)
|
||||
{
|
||||
_userService = userService;
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
@ -19,7 +24,7 @@ namespace Bit.Core.Identity
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var user = await _userRepository.GetByEmailAsync(normalizedEmail);
|
||||
return user?.ToIdentityUser();
|
||||
return user?.ToIdentityUser(await user.TwoFactorIsEnabledAsync(_userService));
|
||||
}
|
||||
|
||||
public override async Task<IdentityUser> FindByIdAsync(string userId,
|
||||
@ -31,7 +36,7 @@ namespace Bit.Core.Identity
|
||||
}
|
||||
|
||||
var user = await _userRepository.GetByIdAsync(userIdGuid);
|
||||
return user?.ToIdentityUser();
|
||||
return user?.ToIdentityUser(await user.TwoFactorIsEnabledAsync(_userService));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ namespace Bit.Core.Identity
|
||||
|
||||
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
||||
{
|
||||
if(!user.Premium)
|
||||
if(!(await _userService.CanAccessPremium(user)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -108,7 +108,7 @@ namespace Bit.Core.Identity
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Bit.Core.Models.Table;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.Core.Identity
|
||||
{
|
||||
@ -14,13 +16,16 @@ namespace Bit.Core.Identity
|
||||
IUserTwoFactorStore<User>,
|
||||
IUserSecurityStampStore<User>
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly CurrentContext _currentContext;
|
||||
|
||||
public UserStore(
|
||||
IServiceProvider serviceProvider,
|
||||
IUserRepository userRepository,
|
||||
CurrentContext currentContext)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_userRepository = userRepository;
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
@ -162,9 +167,9 @@ namespace Bit.Core.Identity
|
||||
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)
|
||||
|
@ -44,7 +44,7 @@ namespace Bit.Core.Identity
|
||||
|
||||
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
|
||||
{
|
||||
if(!user.Premium)
|
||||
if(!(await _userService.CanAccessPremium(user)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -3,12 +3,14 @@ using Bit.Core.Models.Table;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Core.Models.Api
|
||||
{
|
||||
public class ProfileResponseModel : ResponseModel
|
||||
{
|
||||
public ProfileResponseModel(User user, IEnumerable<OrganizationUserOrganizationDetails> organizationsUserDetails)
|
||||
public ProfileResponseModel(User user,
|
||||
IEnumerable<OrganizationUserOrganizationDetails> organizationsUserDetails, bool twoFactorEnabled)
|
||||
: base("profile")
|
||||
{
|
||||
if(user == null)
|
||||
@ -23,7 +25,7 @@ namespace Bit.Core.Models.Api
|
||||
Premium = user.Premium;
|
||||
MasterPasswordHint = string.IsNullOrWhiteSpace(user.MasterPasswordHint) ? null : user.MasterPasswordHint;
|
||||
Culture = user.Culture;
|
||||
TwoFactorEnabled = user.TwoFactorIsEnabled();
|
||||
TwoFactorEnabled = twoFactorEnabled;
|
||||
Key = user.Key;
|
||||
PrivateKey = user.PrivateKey;
|
||||
SecurityStamp = user.SecurityStamp;
|
||||
|
@ -12,6 +12,7 @@ namespace Bit.Core.Models.Api
|
||||
public SyncResponseModel(
|
||||
GlobalSettings globalSettings,
|
||||
User user,
|
||||
bool userTwoFactorEnabled,
|
||||
IEnumerable<OrganizationUserOrganizationDetails> organizationUserDetails,
|
||||
IEnumerable<Folder> folders,
|
||||
IEnumerable<CollectionDetails> collections,
|
||||
@ -20,7 +21,7 @@ namespace Bit.Core.Models.Api
|
||||
bool excludeDomains)
|
||||
: base("sync")
|
||||
{
|
||||
Profile = new ProfileResponseModel(user, organizationUserDetails);
|
||||
Profile = new ProfileResponseModel(user, organizationUserDetails, userTwoFactorEnabled);
|
||||
Folders = folders.Select(f => new FolderResponseModel(f));
|
||||
Ciphers = ciphers.Select(c => new CipherDetailsResponseModel(c, globalSettings, collectionCiphersDict));
|
||||
Collections = collections?.Select(
|
||||
|
@ -3,7 +3,6 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using System.Linq;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Exceptions;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@ -110,7 +109,7 @@ namespace Bit.Core.Models.Table
|
||||
return await userService.CanAccessPremium(this);
|
||||
}
|
||||
|
||||
public bool TwoFactorIsEnabled()
|
||||
public async Task<bool> TwoFactorIsEnabledAsync(IUserService userService)
|
||||
{
|
||||
var providers = GetTwoFactorProviders();
|
||||
if(providers == null)
|
||||
@ -118,8 +117,21 @@ namespace Bit.Core.Models.Table
|
||||
return false;
|
||||
}
|
||||
|
||||
return providers.Any(p => (p.Value?.Enabled ?? false) &&
|
||||
(Premium || !TwoFactorProvider.RequiresPremium(p.Key)));
|
||||
foreach(var p in providers)
|
||||
{
|
||||
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)
|
||||
@ -177,7 +189,7 @@ namespace Bit.Core.Models.Table
|
||||
return paymentService;
|
||||
}
|
||||
|
||||
public IdentityUser ToIdentityUser()
|
||||
public IdentityUser ToIdentityUser(bool twoFactorEnabled)
|
||||
{
|
||||
return new IdentityUser
|
||||
{
|
||||
@ -187,7 +199,7 @@ namespace Bit.Core.Models.Table
|
||||
EmailConfirmed = EmailVerified,
|
||||
UserName = Email,
|
||||
NormalizedUserName = Email,
|
||||
TwoFactorEnabled = TwoFactorIsEnabled(),
|
||||
TwoFactorEnabled = twoFactorEnabled,
|
||||
SecurityStamp = SecurityStamp
|
||||
};
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ namespace Bit.Core.Services
|
||||
private readonly IPushNotificationService _pushService;
|
||||
private readonly IAttachmentStorageService _attachmentStorageService;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
public CipherService(
|
||||
ICipherRepository cipherRepository,
|
||||
@ -35,7 +36,8 @@ namespace Bit.Core.Services
|
||||
ICollectionCipherRepository collectionCipherRepository,
|
||||
IPushNotificationService pushService,
|
||||
IAttachmentStorageService attachmentStorageService,
|
||||
IEventService eventService)
|
||||
IEventService eventService,
|
||||
IUserService userService)
|
||||
{
|
||||
_cipherRepository = cipherRepository;
|
||||
_folderRepository = folderRepository;
|
||||
@ -47,6 +49,7 @@ namespace Bit.Core.Services
|
||||
_pushService = pushService;
|
||||
_attachmentStorageService = attachmentStorageService;
|
||||
_eventService = eventService;
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
public async Task SaveAsync(Cipher cipher, Guid savingUserId, bool orgAdmin = false)
|
||||
@ -125,9 +128,9 @@ namespace Bit.Core.Services
|
||||
if(cipher.UserId.HasValue)
|
||||
{
|
||||
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();
|
||||
|
@ -153,16 +153,13 @@ namespace Bit.Core.Utilities
|
||||
public static IdentityBuilder AddCustomIdentityServices(
|
||||
this IServiceCollection services, GlobalSettings globalSettings)
|
||||
{
|
||||
services.AddTransient<ILookupNormalizer, LowerInvariantLookupNormalizer>();
|
||||
services.AddSingleton<IOrganizationDuoWebTokenProvider, OrganizationDuoWebTokenProvider>();
|
||||
|
||||
services.Configure<PasswordHasherOptions>(options => options.IterationCount = 75000);
|
||||
services.Configure<TwoFactorRememberTokenProviderOptions>(options =>
|
||||
{
|
||||
options.TokenLifespan = TimeSpan.FromDays(30);
|
||||
});
|
||||
|
||||
var identityBuilder = services.AddIdentityWithoutCookieAuth<User, Role>(options =>
|
||||
var identityBuilder = services.AddBasicCustomIdentityServices(globalSettings, options =>
|
||||
{
|
||||
options.User = new UserOptions
|
||||
{
|
||||
@ -187,9 +184,6 @@ namespace Bit.Core.Utilities
|
||||
});
|
||||
|
||||
identityBuilder
|
||||
.AddUserStore<UserStore>()
|
||||
.AddRoleStore<RoleStore>()
|
||||
.AddTokenProvider<DataProtectorTokenProvider<User>>(TokenOptions.DefaultProvider)
|
||||
.AddTokenProvider<AuthenticatorTokenProvider>(TwoFactorProviderType.Authenticator.ToString())
|
||||
.AddTokenProvider<YubicoOtpTokenProvider>(TwoFactorProviderType.YubiKey.ToString())
|
||||
.AddTokenProvider<DuoWebTokenProvider>(TwoFactorProviderType.Duo.ToString())
|
||||
@ -200,11 +194,27 @@ namespace Bit.Core.Utilities
|
||||
return identityBuilder;
|
||||
}
|
||||
|
||||
public static IdentityBuilder AddPasswordlessIdentityServices<TUserStore>(
|
||||
this IServiceCollection services, GlobalSettings globalSettings) where TUserStore : class
|
||||
public static IdentityBuilder AddBasicCustomIdentityServices(
|
||||
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 =>
|
||||
{
|
||||
options.TokenLifespan = TimeSpan.FromMinutes(15);
|
||||
|
Loading…
x
Reference in New Issue
Block a user