mirror of
https://github.com/bitwarden/server.git
synced 2025-05-10 06:02:24 -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 Bit.Core.Enums;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Bit.Api.Controllers
|
namespace Bit.Api.Controllers
|
||||||
{
|
{
|
||||||
@ -262,12 +263,13 @@ namespace Bit.Api.Controllers
|
|||||||
throw new BadRequestException("MasterPasswordHash", "Invalid password.");
|
throw new BadRequestException("MasterPasswordHash", "Invalid password.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await _userService.GetTwoFactorAsync(user, provider);
|
await _userService.SetupTwoFactorAsync(user, provider);
|
||||||
|
|
||||||
var response = new TwoFactorResponseModel(user);
|
var response = new TwoFactorResponseModel(user);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Obsolete]
|
||||||
[HttpPut("two-factor")]
|
[HttpPut("two-factor")]
|
||||||
[HttpPost("two-factor")]
|
[HttpPost("two-factor")]
|
||||||
public async Task<TwoFactorResponseModel> PutTwoFactor([FromBody]UpdateTwoFactorRequestModel model)
|
public async Task<TwoFactorResponseModel> PutTwoFactor([FromBody]UpdateTwoFactorRequestModel model)
|
||||||
@ -290,10 +292,8 @@ namespace Bit.Api.Controllers
|
|||||||
throw new BadRequestException("Token", "Invalid token.");
|
throw new BadRequestException("Token", "Invalid token.");
|
||||||
}
|
}
|
||||||
|
|
||||||
user.TwoFactorProvider = TwoFactorProviderType.Authenticator;
|
|
||||||
user.TwoFactorEnabled = model.Enabled.Value;
|
user.TwoFactorEnabled = model.Enabled.Value;
|
||||||
user.TwoFactorRecoveryCode = user.TwoFactorEnabled ? Guid.NewGuid().ToString("N") : null;
|
await _userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.Authenticator);
|
||||||
await _userService.SaveUserAsync(user);
|
|
||||||
|
|
||||||
var response = new TwoFactorResponseModel(user);
|
var response = new TwoFactorResponseModel(user);
|
||||||
return response;
|
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")]
|
[HttpPut("keys")]
|
||||||
[HttpPost("keys")]
|
[HttpPost("keys")]
|
||||||
public async Task<KeysResponseModel> PutKeys([FromBody]KeysRequestModel model)
|
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)
|
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.HasValue
|
||||||
&& user.TwoFactorProvider.Value == TwoFactorProviderType.Authenticator
|
&& user.TwoFactorProvider.Value == TwoFactorProviderType.Authenticator
|
||||||
&& !string.IsNullOrWhiteSpace(user.AuthenticatorKey);
|
&& !string.IsNullOrWhiteSpace(provider.MetaData["Key"]);
|
||||||
|
|
||||||
return Task.FromResult(canGenerate);
|
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)
|
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;
|
long timeStepMatched;
|
||||||
var valid = otp.VerifyTotp(token, out timeStepMatched, new VerificationWindow(1, 1));
|
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)
|
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)
|
public Task SetSecurityStampAsync(User user, string stamp, CancellationToken cancellationToken)
|
||||||
|
@ -22,7 +22,7 @@ namespace Bit.Core.Models.Api
|
|||||||
Email = user.Email;
|
Email = user.Email;
|
||||||
MasterPasswordHint = string.IsNullOrWhiteSpace(user.MasterPasswordHint) ? null : user.MasterPasswordHint;
|
MasterPasswordHint = string.IsNullOrWhiteSpace(user.MasterPasswordHint) ? null : user.MasterPasswordHint;
|
||||||
Culture = user.Culture;
|
Culture = user.Culture;
|
||||||
TwoFactorEnabled = user.TwoFactorEnabled;
|
TwoFactorEnabled = user.TwoFactorIsEnabled();
|
||||||
Key = user.Key;
|
Key = user.Key;
|
||||||
PrivateKey = user.PrivateKey;
|
PrivateKey = user.PrivateKey;
|
||||||
SecurityStamp = user.SecurityStamp;
|
SecurityStamp = user.SecurityStamp;
|
||||||
|
@ -14,8 +14,25 @@ namespace Bit.Core.Models.Api
|
|||||||
throw new ArgumentNullException(nameof(user));
|
throw new ArgumentNullException(nameof(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
TwoFactorEnabled = user.TwoFactorEnabled;
|
var providers = user.GetTwoFactorProviders();
|
||||||
AuthenticatorKey = user.AuthenticatorKey;
|
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;
|
TwoFactorProvider = user.TwoFactorProvider;
|
||||||
TwoFactorRecoveryCode = user.TwoFactorRecoveryCode;
|
TwoFactorRecoveryCode = user.TwoFactorRecoveryCode;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
using System;
|
using System;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace Bit.Core.Models.Table
|
namespace Bit.Core.Models.Table
|
||||||
{
|
{
|
||||||
public class User : IDataObject<Guid>
|
public class User : IDataObject<Guid>
|
||||||
{
|
{
|
||||||
|
private Dictionary<TwoFactorProviderType, TwoFactorProvider> _twoFactorProviders;
|
||||||
|
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Email { get; set; }
|
public string Email { get; set; }
|
||||||
@ -32,5 +36,69 @@ namespace Bit.Core.Models.Table
|
|||||||
{
|
{
|
||||||
Id = CoreHelpers.GenerateComb();
|
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 Enabled { get; set; }
|
||||||
public bool Remember { 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 Microsoft.AspNetCore.Identity;
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@ -24,7 +26,8 @@ namespace Bit.Core.Services
|
|||||||
Task<IdentityResult> UpdateKeyAsync(User user, string masterPassword, string key, string privateKey,
|
Task<IdentityResult> UpdateKeyAsync(User user, string masterPassword, string key, string privateKey,
|
||||||
IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
|
IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders);
|
||||||
Task<IdentityResult> RefreshSecurityStampAsync(User user, string masterPasswordHash);
|
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<bool> RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode);
|
||||||
Task<string> GenerateUserTokenAsync(User user, string tokenProvider, string purpose);
|
Task<string> GenerateUserTokenAsync(User user, string tokenProvider, string purpose);
|
||||||
Task<IdentityResult> DeleteAsync(User user);
|
Task<IdentityResult> DeleteAsync(User user);
|
||||||
|
@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Builder;
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using OtpNet;
|
using OtpNet;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
using Bit.Core.Models;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
@ -315,14 +316,16 @@ namespace Bit.Core.Services
|
|||||||
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
|
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)
|
switch(provider)
|
||||||
{
|
{
|
||||||
case TwoFactorProviderType.Authenticator:
|
case TwoFactorProviderType.Authenticator:
|
||||||
if(!string.IsNullOrWhiteSpace(user.AuthenticatorKey))
|
if(!string.IsNullOrWhiteSpace(providers[provider].MetaData["Key"]))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -332,20 +335,51 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user.TwoFactorProvider = provider;
|
if(providers == null)
|
||||||
// Reset authenticator key.
|
{
|
||||||
user.AuthenticatorKey = 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)
|
switch(provider)
|
||||||
{
|
{
|
||||||
case TwoFactorProviderType.Authenticator:
|
case TwoFactorProviderType.Authenticator:
|
||||||
var key = KeyGeneration.GenerateRandomKey(20);
|
var key = KeyGeneration.GenerateRandomKey(20);
|
||||||
user.AuthenticatorKey = Base32Encoding.ToString(key);
|
providerInfo.MetaData["Key"] = Base32Encoding.ToString(key);
|
||||||
|
providerInfo.Remember = true;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ArgumentException(nameof(provider));
|
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);
|
await SaveUserAsync(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,7 +402,6 @@ namespace Bit.Core.Services
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.TwoFactorProvider = TwoFactorProviderType.Authenticator;
|
|
||||||
user.TwoFactorEnabled = false;
|
user.TwoFactorEnabled = false;
|
||||||
user.TwoFactorRecoveryCode = null;
|
user.TwoFactorRecoveryCode = null;
|
||||||
await SaveUserAsync(user);
|
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