1
0
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:
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 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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