1
0
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:
Kyle Spearrin 2017-06-07 14:14:34 -04:00
parent d8c0994ed3
commit ecc2468409
10 changed files with 166 additions and 53 deletions

View File

@ -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)

View File

@ -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));

View File

@ -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)

View File

@ -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;

View File

@ -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;
}

View File

@ -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];
}
}
}

View File

@ -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>();
}
}

View File

@ -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);

View File

@ -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);

View 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;
}
}
}