mirror of
https://github.com/bitwarden/server.git
synced 2025-05-09 21:52:17 -05:00
refactor code to with user TwoFactorProviders
This commit is contained in:
parent
d8c0994ed3
commit
ecc2468409
@ -10,6 +10,7 @@ using Bit.Core.Models.Table;
|
||||
using Bit.Core.Enums;
|
||||
using System.Linq;
|
||||
using Bit.Core.Repositories;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.Api.Controllers
|
||||
{
|
||||
@ -262,12 +263,13 @@ namespace Bit.Api.Controllers
|
||||
throw new BadRequestException("MasterPasswordHash", "Invalid password.");
|
||||
}
|
||||
|
||||
await _userService.GetTwoFactorAsync(user, provider);
|
||||
await _userService.SetupTwoFactorAsync(user, provider);
|
||||
|
||||
var response = new TwoFactorResponseModel(user);
|
||||
return response;
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
[HttpPut("two-factor")]
|
||||
[HttpPost("two-factor")]
|
||||
public async Task<TwoFactorResponseModel> PutTwoFactor([FromBody]UpdateTwoFactorRequestModel model)
|
||||
@ -290,10 +292,8 @@ namespace Bit.Api.Controllers
|
||||
throw new BadRequestException("Token", "Invalid token.");
|
||||
}
|
||||
|
||||
user.TwoFactorProvider = TwoFactorProviderType.Authenticator;
|
||||
user.TwoFactorEnabled = model.Enabled.Value;
|
||||
user.TwoFactorRecoveryCode = user.TwoFactorEnabled ? Guid.NewGuid().ToString("N") : null;
|
||||
await _userService.SaveUserAsync(user);
|
||||
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Authenticator);
|
||||
|
||||
var response = new TwoFactorResponseModel(user);
|
||||
return response;
|
||||
@ -310,38 +310,6 @@ namespace Bit.Api.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("two-factor-regenerate")]
|
||||
[HttpPost("two-factor-regenerate")]
|
||||
public async Task<TwoFactorResponseModel> PutTwoFactorRegenerate([FromBody]RegenerateTwoFactorRequestModel model)
|
||||
{
|
||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||
if(user == null)
|
||||
{
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
if(!await _userManager.CheckPasswordAsync(user, model.MasterPasswordHash))
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException("MasterPasswordHash", "Invalid password.");
|
||||
}
|
||||
|
||||
if(!await _userManager.VerifyTwoFactorTokenAsync(user, TwoFactorProviderType.Authenticator.ToString(), model.Token))
|
||||
{
|
||||
await Task.Delay(2000);
|
||||
throw new BadRequestException("Token", "Invalid token.");
|
||||
}
|
||||
|
||||
if(user.TwoFactorEnabled)
|
||||
{
|
||||
user.TwoFactorRecoveryCode = Guid.NewGuid().ToString("N");
|
||||
await _userService.SaveUserAsync(user);
|
||||
}
|
||||
|
||||
var response = new TwoFactorResponseModel(user);
|
||||
return response;
|
||||
}
|
||||
|
||||
[HttpPut("keys")]
|
||||
[HttpPost("keys")]
|
||||
public async Task<KeysResponseModel> PutKeys([FromBody]KeysRequestModel model)
|
||||
|
@ -11,10 +11,12 @@ namespace Bit.Core.Identity
|
||||
{
|
||||
public Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
||||
{
|
||||
var canGenerate = user.TwoFactorEnabled
|
||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator);
|
||||
|
||||
var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.Authenticator)
|
||||
&& user.TwoFactorProvider.HasValue
|
||||
&& user.TwoFactorProvider.Value == TwoFactorProviderType.Authenticator
|
||||
&& !string.IsNullOrWhiteSpace(user.AuthenticatorKey);
|
||||
&& !string.IsNullOrWhiteSpace(provider.MetaData["Key"]);
|
||||
|
||||
return Task.FromResult(canGenerate);
|
||||
}
|
||||
@ -31,7 +33,8 @@ namespace Bit.Core.Identity
|
||||
|
||||
public Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
|
||||
{
|
||||
var otp = new Totp(Base32Encoding.ToBytes(user.AuthenticatorKey));
|
||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator);
|
||||
var otp = new Totp(Base32Encoding.ToBytes(provider.MetaData["Key"]));
|
||||
|
||||
long timeStepMatched;
|
||||
var valid = otp.VerifyTotp(token, out timeStepMatched, new VerificationWindow(1, 1));
|
||||
|
@ -168,7 +168,7 @@ namespace Bit.Core.Identity
|
||||
|
||||
public Task<bool> GetTwoFactorEnabledAsync(User user, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(user.TwoFactorEnabled && user.TwoFactorProvider.HasValue);
|
||||
return Task.FromResult(user.TwoFactorIsEnabled());
|
||||
}
|
||||
|
||||
public Task SetSecurityStampAsync(User user, string stamp, CancellationToken cancellationToken)
|
||||
|
@ -22,7 +22,7 @@ namespace Bit.Core.Models.Api
|
||||
Email = user.Email;
|
||||
MasterPasswordHint = string.IsNullOrWhiteSpace(user.MasterPasswordHint) ? null : user.MasterPasswordHint;
|
||||
Culture = user.Culture;
|
||||
TwoFactorEnabled = user.TwoFactorEnabled;
|
||||
TwoFactorEnabled = user.TwoFactorIsEnabled();
|
||||
Key = user.Key;
|
||||
PrivateKey = user.PrivateKey;
|
||||
SecurityStamp = user.SecurityStamp;
|
||||
|
@ -14,8 +14,25 @@ namespace Bit.Core.Models.Api
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
}
|
||||
|
||||
TwoFactorEnabled = user.TwoFactorEnabled;
|
||||
AuthenticatorKey = user.AuthenticatorKey;
|
||||
var providers = user.GetTwoFactorProviders();
|
||||
if(user.TwoFactorProvider.HasValue && providers.ContainsKey(user.TwoFactorProvider.Value))
|
||||
{
|
||||
var provider = providers[user.TwoFactorProvider.Value];
|
||||
switch(user.TwoFactorProvider.Value)
|
||||
{
|
||||
case TwoFactorProviderType.Authenticator:
|
||||
AuthenticatorKey = provider.MetaData["Key"];
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TwoFactorEnabled = false;
|
||||
}
|
||||
|
||||
TwoFactorEnabled = user.TwoFactorIsEnabled();
|
||||
TwoFactorProvider = user.TwoFactorProvider;
|
||||
TwoFactorRecoveryCode = user.TwoFactorRecoveryCode;
|
||||
}
|
||||
|
@ -1,11 +1,15 @@
|
||||
using System;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Core.Models.Table
|
||||
{
|
||||
public class User : IDataObject<Guid>
|
||||
{
|
||||
private Dictionary<TwoFactorProviderType, TwoFactorProvider> _twoFactorProviders;
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Email { get; set; }
|
||||
@ -32,5 +36,69 @@ namespace Bit.Core.Models.Table
|
||||
{
|
||||
Id = CoreHelpers.GenerateComb();
|
||||
}
|
||||
|
||||
public Dictionary<TwoFactorProviderType, TwoFactorProvider> GetTwoFactorProviders()
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(TwoFactorProviders))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(_twoFactorProviders == null)
|
||||
{
|
||||
_twoFactorProviders =
|
||||
JsonConvert.DeserializeObject<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(TwoFactorProviders);
|
||||
}
|
||||
|
||||
return _twoFactorProviders;
|
||||
}
|
||||
catch(JsonSerializationException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTwoFactorProviders(Dictionary<TwoFactorProviderType, TwoFactorProvider> providers)
|
||||
{
|
||||
TwoFactorProviders = JsonConvert.SerializeObject(providers, new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new EnumKeyResolver<byte>()
|
||||
});
|
||||
_twoFactorProviders = providers;
|
||||
}
|
||||
|
||||
public bool TwoFactorProviderIsEnabled(TwoFactorProviderType provider)
|
||||
{
|
||||
var providers = GetTwoFactorProviders();
|
||||
if(providers == null || !providers.ContainsKey(provider))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return providers[provider].Enabled;
|
||||
}
|
||||
|
||||
public bool TwoFactorIsEnabled(TwoFactorProviderType provider)
|
||||
{
|
||||
return TwoFactorEnabled && TwoFactorProviderIsEnabled(provider);
|
||||
}
|
||||
|
||||
public bool TwoFactorIsEnabled()
|
||||
{
|
||||
return TwoFactorEnabled && TwoFactorProvider.HasValue && TwoFactorProviderIsEnabled(TwoFactorProvider.Value);
|
||||
}
|
||||
|
||||
public TwoFactorProvider GetTwoFactorProvider(TwoFactorProviderType provider)
|
||||
{
|
||||
var providers = GetTwoFactorProviders();
|
||||
if(providers == null || !providers.ContainsKey(provider))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return providers[provider];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,6 @@ namespace Bit.Core.Models
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public bool Remember { get; set; }
|
||||
public Dictionary<string, object> MetaData { get; set; }
|
||||
public Dictionary<string, string> MetaData { get; set; } = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Bit.Core.Models.Table;
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
@ -24,7 +26,8 @@ namespace Bit.Core.Services
|
||||
Task<IdentityResult> UpdateKeyAsync(User user, string masterPassword, string key, string privateKey,
|
||||
IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
|
||||
Task<IdentityResult> RefreshSecurityStampAsync(User user, string masterPasswordHash);
|
||||
Task GetTwoFactorAsync(User user, Enums.TwoFactorProviderType provider);
|
||||
Task SetupTwoFactorAsync(User user, TwoFactorProviderType provider);
|
||||
Task UpdateTwoFactorProviderAsync(User user, TwoFactorProviderType type);
|
||||
Task<bool> RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode);
|
||||
Task<string> GenerateUserTokenAsync(User user, string tokenProvider, string purpose);
|
||||
Task<IdentityResult> DeleteAsync(User user);
|
||||
|
@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Builder;
|
||||
using Bit.Core.Enums;
|
||||
using OtpNet;
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Models;
|
||||
|
||||
namespace Bit.Core.Services
|
||||
{
|
||||
@ -315,14 +316,16 @@ namespace Bit.Core.Services
|
||||
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
|
||||
}
|
||||
|
||||
public async Task GetTwoFactorAsync(User user, TwoFactorProviderType provider)
|
||||
public async Task SetupTwoFactorAsync(User user, TwoFactorProviderType provider)
|
||||
{
|
||||
if(user.TwoFactorEnabled && user.TwoFactorProvider.HasValue && user.TwoFactorProvider.Value == provider)
|
||||
var providers = user.GetTwoFactorProviders();
|
||||
if(providers != null && providers.ContainsKey(provider) && providers[provider].Enabled &&
|
||||
user.TwoFactorProvider.HasValue && user.TwoFactorProvider.Value == provider)
|
||||
{
|
||||
switch(provider)
|
||||
{
|
||||
case TwoFactorProviderType.Authenticator:
|
||||
if(!string.IsNullOrWhiteSpace(user.AuthenticatorKey))
|
||||
if(!string.IsNullOrWhiteSpace(providers[provider].MetaData["Key"]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -332,20 +335,51 @@ namespace Bit.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
user.TwoFactorProvider = provider;
|
||||
// Reset authenticator key.
|
||||
user.AuthenticatorKey = null;
|
||||
if(providers == null)
|
||||
{
|
||||
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
|
||||
}
|
||||
|
||||
TwoFactorProvider providerInfo = null;
|
||||
if(!providers.ContainsKey(provider))
|
||||
{
|
||||
providerInfo = new TwoFactorProvider();
|
||||
providers.Add(provider, providerInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
providerInfo = providers[provider];
|
||||
}
|
||||
|
||||
switch(provider)
|
||||
{
|
||||
case TwoFactorProviderType.Authenticator:
|
||||
var key = KeyGeneration.GenerateRandomKey(20);
|
||||
user.AuthenticatorKey = Base32Encoding.ToString(key);
|
||||
providerInfo.MetaData["Key"] = Base32Encoding.ToString(key);
|
||||
providerInfo.Remember = true;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException(nameof(provider));
|
||||
}
|
||||
|
||||
user.TwoFactorProvider = provider;
|
||||
user.SetTwoFactorProviders(providers);
|
||||
await SaveUserAsync(user);
|
||||
}
|
||||
|
||||
public async Task UpdateTwoFactorProviderAsync(User user, TwoFactorProviderType type)
|
||||
{
|
||||
var providers = user.GetTwoFactorProviders();
|
||||
if(!providers?.ContainsKey(type) ?? true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
providers[type].Enabled = user.TwoFactorEnabled;
|
||||
user.SetTwoFactorProviders(providers);
|
||||
|
||||
user.TwoFactorProvider = type;
|
||||
user.TwoFactorRecoveryCode = user.TwoFactorIsEnabled() ? Guid.NewGuid().ToString("N") : null;
|
||||
await SaveUserAsync(user);
|
||||
}
|
||||
|
||||
@ -368,7 +402,6 @@ namespace Bit.Core.Services
|
||||
return false;
|
||||
}
|
||||
|
||||
user.TwoFactorProvider = TwoFactorProviderType.Authenticator;
|
||||
user.TwoFactorEnabled = false;
|
||||
user.TwoFactorRecoveryCode = null;
|
||||
await SaveUserAsync(user);
|
||||
|
21
src/Core/Utilities/EnumKeyResolver.cs
Normal file
21
src/Core/Utilities/EnumKeyResolver.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
|
||||
namespace Bit.Core.Utilities
|
||||
{
|
||||
public class EnumKeyResolver<T> : DefaultContractResolver where T : struct
|
||||
{
|
||||
protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
|
||||
{
|
||||
var contract = base.CreateDictionaryContract(objectType);
|
||||
var keyType = contract.DictionaryKeyType;
|
||||
|
||||
if(keyType.BaseType == typeof(Enum))
|
||||
{
|
||||
contract.DictionaryKeyResolver = propName => ((T)Enum.Parse(keyType, propName)).ToString();
|
||||
}
|
||||
|
||||
return contract;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user