1
0
mirror of https://github.com/bitwarden/server synced 2025-12-25 04:33:26 +00:00

[PM-1188] Server owner auth migration (#2825)

* [PM-1188] add sso project to auth

* [PM-1188] move sso api models to auth

* [PM-1188] fix sso api model namespace & imports

* [PM-1188] move core files to auth

* [PM-1188] fix core sso namespace & models

* [PM-1188] move sso repository files to auth

* [PM-1188] fix sso repo files namespace & imports

* [PM-1188] move sso sql files to auth folder

* [PM-1188] move sso test files to auth folders

* [PM-1188] fix sso tests namespace & imports

* [PM-1188] move auth api files to auth folder

* [PM-1188] fix auth api files namespace & imports

* [PM-1188] move auth core files to auth folder

* [PM-1188] fix auth core files namespace & imports

* [PM-1188] move auth email templates to auth folder

* [PM-1188] move auth email folder back into shared directory

* [PM-1188] fix auth email names

* [PM-1188] move auth core models to auth folder

* [PM-1188] fix auth model namespace & imports

* [PM-1188] add entire Identity project to auth codeowners

* [PM-1188] fix auth orm files namespace & imports

* [PM-1188] move auth orm files to auth folder

* [PM-1188] move auth sql files to auth folder

* [PM-1188] move auth tests to auth folder

* [PM-1188] fix auth test files namespace & imports

* [PM-1188] move emergency access api files to auth folder

* [PM-1188] fix emergencyaccess api files namespace & imports

* [PM-1188] move emergency access core files to auth folder

* [PM-1188] fix emergency access core files namespace & imports

* [PM-1188] move emergency access orm files to auth folder

* [PM-1188] fix emergency access orm files namespace & imports

* [PM-1188] move emergency access sql files to auth folder

* [PM-1188] move emergencyaccess test files to auth folder

* [PM-1188] fix emergency access test files namespace & imports

* [PM-1188] move captcha files to auth folder

* [PM-1188] fix captcha files namespace & imports

* [PM-1188] move auth admin files into auth folder

* [PM-1188] fix admin auth files namespace & imports
- configure mvc to look in auth folders for views

* [PM-1188] remove extra imports and formatting

* [PM-1188] fix ef auth model imports

* [PM-1188] fix DatabaseContextModelSnapshot paths

* [PM-1188] fix grant import in ef

* [PM-1188] update sqlproj

* [PM-1188] move missed sqlproj files

* [PM-1188] move auth ef models out of auth folder

* [PM-1188] fix auth ef models namespace

* [PM-1188] remove auth ef models unused imports

* [PM-1188] fix imports for auth ef models

* [PM-1188] fix more ef model imports

* [PM-1188] fix file encodings
This commit is contained in:
Jake Fink
2023-04-14 13:25:56 -04:00
committed by GitHub
parent 2529c5b36f
commit 88dd745070
332 changed files with 704 additions and 522 deletions

View File

@@ -1,45 +0,0 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using OtpNet;
namespace Bit.Core.Identity;
public class AuthenticatorTokenProvider : IUserTwoFactorTokenProvider<User>
{
private readonly IServiceProvider _serviceProvider;
public AuthenticatorTokenProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
{
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator);
if (string.IsNullOrWhiteSpace((string)provider?.MetaData["Key"]))
{
return false;
}
return await _serviceProvider.GetRequiredService<IUserService>()
.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Authenticator, user);
}
public Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
{
return Task.FromResult<string>(null);
}
public Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
{
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator);
var otp = new Totp(Base32Encoding.ToBytes((string)provider.MetaData["Key"]));
long timeStepMatched;
var valid = otp.VerifyTotp(token, out timeStepMatched, new VerificationWindow(1, 1));
return Task.FromResult(valid);
}
}

View File

@@ -1,4 +1,4 @@
using Bit.Core.Identity;
using Bit.Core.Auth.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection.Extensions;

View File

@@ -1,86 +0,0 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities.Duo;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.Identity;
public class DuoWebTokenProvider : IUserTwoFactorTokenProvider<User>
{
private readonly IServiceProvider _serviceProvider;
private readonly GlobalSettings _globalSettings;
public DuoWebTokenProvider(
IServiceProvider serviceProvider,
GlobalSettings globalSettings)
{
_serviceProvider = serviceProvider;
_globalSettings = globalSettings;
}
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
if (!(await userService.CanAccessPremium(user)))
{
return false;
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo);
if (!HasProperMetaData(provider))
{
return false;
}
return await userService.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Duo, user);
}
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
if (!(await userService.CanAccessPremium(user)))
{
return null;
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo);
if (!HasProperMetaData(provider))
{
return null;
}
var signatureRequest = DuoWeb.SignRequest((string)provider.MetaData["IKey"],
(string)provider.MetaData["SKey"], _globalSettings.Duo.AKey, user.Email);
return signatureRequest;
}
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
if (!(await userService.CanAccessPremium(user)))
{
return false;
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo);
if (!HasProperMetaData(provider))
{
return false;
}
var response = DuoWeb.VerifyResponse((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"],
_globalSettings.Duo.AKey, token);
return response == user.Email;
}
private bool HasProperMetaData(TwoFactorProvider provider)
{
return provider?.MetaData != null && provider.MetaData.ContainsKey("IKey") &&
provider.MetaData.ContainsKey("SKey") && provider.MetaData.ContainsKey("Host");
}
}

View File

@@ -1,83 +0,0 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.Identity;
public class EmailTokenProvider : IUserTwoFactorTokenProvider<User>
{
private readonly IServiceProvider _serviceProvider;
public EmailTokenProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
{
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email);
if (!HasProperMetaData(provider))
{
return false;
}
return await _serviceProvider.GetRequiredService<IUserService>().
TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Email, user);
}
public Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
{
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email);
if (!HasProperMetaData(provider))
{
return null;
}
return Task.FromResult(RedactEmail((string)provider.MetaData["Email"]));
}
public Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
{
return _serviceProvider.GetRequiredService<IUserService>().VerifyTwoFactorEmailAsync(user, token);
}
private bool HasProperMetaData(TwoFactorProvider provider)
{
return provider?.MetaData != null && provider.MetaData.ContainsKey("Email") &&
!string.IsNullOrWhiteSpace((string)provider.MetaData["Email"]);
}
private static string RedactEmail(string email)
{
var emailParts = email.Split('@');
string shownPart = null;
if (emailParts[0].Length > 2 && emailParts[0].Length <= 4)
{
shownPart = emailParts[0].Substring(0, 1);
}
else if (emailParts[0].Length > 4)
{
shownPart = emailParts[0].Substring(0, 2);
}
else
{
shownPart = string.Empty;
}
string redactedPart = null;
if (emailParts[0].Length > 4)
{
redactedPart = new string('*', emailParts[0].Length - 2);
}
else
{
redactedPart = new string('*', emailParts[0].Length - shownPart.Length);
}
return $"{shownPart}{redactedPart}@{emailParts[1]}";
}
}

View File

@@ -1,10 +0,0 @@
using Bit.Core.Entities;
namespace Bit.Core.Identity;
public interface IOrganizationTwoFactorTokenProvider
{
Task<bool> CanGenerateTwoFactorTokenAsync(Organization organization);
Task<string> GenerateAsync(Organization organization, User user);
Task<bool> ValidateAsync(string token, Organization organization, User user);
}

View File

@@ -1,21 +0,0 @@
using Microsoft.AspNetCore.Identity;
namespace Bit.Core.Identity;
public class LowerInvariantLookupNormalizer : ILookupNormalizer
{
public string NormalizeEmail(string email)
{
return Normalize(email);
}
public string NormalizeName(string name)
{
return Normalize(name);
}
private string Normalize(string key)
{
return key?.Normalize().ToLowerInvariant();
}
}

View File

@@ -1,75 +0,0 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Settings;
using Bit.Core.Utilities.Duo;
namespace Bit.Core.Identity;
public interface IOrganizationDuoWebTokenProvider : IOrganizationTwoFactorTokenProvider { }
public class OrganizationDuoWebTokenProvider : IOrganizationDuoWebTokenProvider
{
private readonly GlobalSettings _globalSettings;
public OrganizationDuoWebTokenProvider(GlobalSettings globalSettings)
{
_globalSettings = globalSettings;
}
public Task<bool> CanGenerateTwoFactorTokenAsync(Organization organization)
{
if (organization == null || !organization.Enabled || !organization.Use2fa)
{
return Task.FromResult(false);
}
var provider = organization.GetTwoFactorProvider(TwoFactorProviderType.OrganizationDuo);
var canGenerate = organization.TwoFactorProviderIsEnabled(TwoFactorProviderType.OrganizationDuo)
&& HasProperMetaData(provider);
return Task.FromResult(canGenerate);
}
public Task<string> GenerateAsync(Organization organization, User user)
{
if (organization == null || !organization.Enabled || !organization.Use2fa)
{
return Task.FromResult<string>(null);
}
var provider = organization.GetTwoFactorProvider(TwoFactorProviderType.OrganizationDuo);
if (!HasProperMetaData(provider))
{
return Task.FromResult<string>(null);
}
var signatureRequest = DuoWeb.SignRequest(provider.MetaData["IKey"].ToString(),
provider.MetaData["SKey"].ToString(), _globalSettings.Duo.AKey, user.Email);
return Task.FromResult(signatureRequest);
}
public Task<bool> ValidateAsync(string token, Organization organization, User user)
{
if (organization == null || !organization.Enabled || !organization.Use2fa)
{
return Task.FromResult(false);
}
var provider = organization.GetTwoFactorProvider(TwoFactorProviderType.OrganizationDuo);
if (!HasProperMetaData(provider))
{
return Task.FromResult(false);
}
var response = DuoWeb.VerifyResponse(provider.MetaData["IKey"].ToString(),
provider.MetaData["SKey"].ToString(), _globalSettings.Duo.AKey, token);
return Task.FromResult(response == user.Email);
}
private bool HasProperMetaData(TwoFactorProvider provider)
{
return provider?.MetaData != null && provider.MetaData.ContainsKey("IKey") &&
provider.MetaData.ContainsKey("SKey") && provider.MetaData.ContainsKey("Host");
}
}

View File

@@ -1,60 +0,0 @@
using Bit.Core.Entities;
using Microsoft.AspNetCore.Identity;
namespace Bit.Core.Identity;
public class RoleStore : IRoleStore<Role>
{
public void Dispose() { }
public Task<IdentityResult> CreateAsync(Role role, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<IdentityResult> DeleteAsync(Role role, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<Role> FindByIdAsync(string roleId, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<Role> FindByNameAsync(string normalizedRoleName, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string> GetNormalizedRoleNameAsync(Role role, CancellationToken cancellationToken)
{
return Task.FromResult(role.Name);
}
public Task<string> GetRoleIdAsync(Role role, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task<string> GetRoleNameAsync(Role role, CancellationToken cancellationToken)
{
return Task.FromResult(role.Name);
}
public Task SetNormalizedRoleNameAsync(Role role, string normalizedName, CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
public Task SetRoleNameAsync(Role role, string roleName, CancellationToken cancellationToken)
{
role.Name = roleName;
return Task.FromResult(0);
}
public Task<IdentityResult> UpdateAsync(Role role, CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}

View File

@@ -1,20 +0,0 @@
using Bit.Core.Entities;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Bit.Core.Identity;
public class TwoFactorRememberTokenProvider : DataProtectorTokenProvider<User>
{
public TwoFactorRememberTokenProvider(
IDataProtectionProvider dataProtectionProvider,
IOptions<TwoFactorRememberTokenProviderOptions> options,
ILogger<DataProtectorTokenProvider<User>> logger)
: base(dataProtectionProvider, options, logger)
{ }
}
public class TwoFactorRememberTokenProviderOptions : DataProtectionTokenProviderOptions
{ }

View File

@@ -1,183 +0,0 @@
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.Identity;
public class UserStore :
IUserStore<User>,
IUserPasswordStore<User>,
IUserEmailStore<User>,
IUserTwoFactorStore<User>,
IUserSecurityStampStore<User>
{
private readonly IServiceProvider _serviceProvider;
private readonly IUserRepository _userRepository;
private readonly ICurrentContext _currentContext;
public UserStore(
IServiceProvider serviceProvider,
IUserRepository userRepository,
ICurrentContext currentContext)
{
_serviceProvider = serviceProvider;
_userRepository = userRepository;
_currentContext = currentContext;
}
public void Dispose() { }
public async Task<IdentityResult> CreateAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
{
await _userRepository.CreateAsync(user);
return IdentityResult.Success;
}
public async Task<IdentityResult> DeleteAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
{
await _userRepository.DeleteAsync(user);
return IdentityResult.Success;
}
public async Task<User> FindByEmailAsync(string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken))
{
if (_currentContext?.User != null && _currentContext.User.Email == normalizedEmail)
{
return _currentContext.User;
}
_currentContext.User = await _userRepository.GetByEmailAsync(normalizedEmail);
return _currentContext.User;
}
public async Task<User> FindByIdAsync(string userId, CancellationToken cancellationToken = default(CancellationToken))
{
if (_currentContext?.User != null &&
string.Equals(_currentContext.User.Id.ToString(), userId, StringComparison.InvariantCultureIgnoreCase))
{
return _currentContext.User;
}
Guid userIdGuid;
if (!Guid.TryParse(userId, out userIdGuid))
{
return null;
}
_currentContext.User = await _userRepository.GetByIdAsync(userIdGuid);
return _currentContext.User;
}
public async Task<User> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default(CancellationToken))
{
return await FindByEmailAsync(normalizedUserName, cancellationToken);
}
public Task<string> GetEmailAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.Email);
}
public Task<bool> GetEmailConfirmedAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.EmailVerified);
}
public Task<string> GetNormalizedEmailAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.Email);
}
public Task<string> GetNormalizedUserNameAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.Email);
}
public Task<string> GetPasswordHashAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.MasterPassword);
}
public Task<string> GetUserIdAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.Id.ToString());
}
public Task<string> GetUserNameAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(user.Email);
}
public Task<bool> HasPasswordAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
{
return Task.FromResult(!string.IsNullOrWhiteSpace(user.MasterPassword));
}
public Task SetEmailAsync(User user, string email, CancellationToken cancellationToken = default(CancellationToken))
{
user.Email = email;
return Task.FromResult(0);
}
public Task SetEmailConfirmedAsync(User user, bool confirmed, CancellationToken cancellationToken = default(CancellationToken))
{
user.EmailVerified = confirmed;
return Task.FromResult(0);
}
public Task SetNormalizedEmailAsync(User user, string normalizedEmail, CancellationToken cancellationToken = default(CancellationToken))
{
user.Email = normalizedEmail;
return Task.FromResult(0);
}
public Task SetNormalizedUserNameAsync(User user, string normalizedName, CancellationToken cancellationToken = default(CancellationToken))
{
user.Email = normalizedName;
return Task.FromResult(0);
}
public Task SetPasswordHashAsync(User user, string passwordHash, CancellationToken cancellationToken = default(CancellationToken))
{
user.MasterPassword = passwordHash;
return Task.FromResult(0);
}
public Task SetUserNameAsync(User user, string userName, CancellationToken cancellationToken = default(CancellationToken))
{
user.Email = userName;
return Task.FromResult(0);
}
public async Task<IdentityResult> UpdateAsync(User user, CancellationToken cancellationToken = default(CancellationToken))
{
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
await _userRepository.ReplaceAsync(user);
return IdentityResult.Success;
}
public Task SetTwoFactorEnabledAsync(User user, bool enabled, CancellationToken cancellationToken)
{
// Do nothing...
return Task.FromResult(0);
}
public async Task<bool> GetTwoFactorEnabledAsync(User user, CancellationToken cancellationToken)
{
return await _serviceProvider.GetRequiredService<IUserService>().TwoFactorIsEnabledAsync(user);
}
public Task SetSecurityStampAsync(User user, string stamp, CancellationToken cancellationToken)
{
user.SecurityStamp = stamp;
return Task.FromResult(0);
}
public Task<string> GetSecurityStampAsync(User user, CancellationToken cancellationToken)
{
return Task.FromResult(user.SecurityStamp);
}
}

View File

@@ -1,156 +0,0 @@
using System.Text.Json;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Fido2NetLib;
using Fido2NetLib.Objects;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
namespace Bit.Core.Identity;
public class WebAuthnTokenProvider : IUserTwoFactorTokenProvider<User>
{
private readonly IServiceProvider _serviceProvider;
private readonly IFido2 _fido2;
private readonly GlobalSettings _globalSettings;
public WebAuthnTokenProvider(IServiceProvider serviceProvider, IFido2 fido2, GlobalSettings globalSettings)
{
_serviceProvider = serviceProvider;
_fido2 = fido2;
_globalSettings = globalSettings;
}
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
if (!(await userService.CanAccessPremium(user)))
{
return false;
}
var webAuthnProvider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
if (!HasProperMetaData(webAuthnProvider))
{
return false;
}
return await userService.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.WebAuthn, user);
}
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
if (!(await userService.CanAccessPremium(user)))
{
return null;
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
var keys = LoadKeys(provider);
var existingCredentials = keys.Select(key => key.Item2.Descriptor).ToList();
if (existingCredentials.Count == 0)
{
return null;
}
var exts = new AuthenticationExtensionsClientInputs()
{
UserVerificationMethod = true,
AppID = CoreHelpers.U2fAppIdUrl(_globalSettings),
};
var options = _fido2.GetAssertionOptions(existingCredentials, UserVerificationRequirement.Discouraged, exts);
// TODO: Remove this when newtonsoft legacy converters are gone
provider.MetaData["login"] = JsonSerializer.Serialize(options);
var providers = user.GetTwoFactorProviders();
providers[TwoFactorProviderType.WebAuthn] = provider;
user.SetTwoFactorProviders(providers);
await userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.WebAuthn, logEvent: false);
return options.ToJson();
}
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
if (!(await userService.CanAccessPremium(user)) || string.IsNullOrWhiteSpace(token))
{
return false;
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
var keys = LoadKeys(provider);
if (!provider.MetaData.ContainsKey("login"))
{
return false;
}
var clientResponse = JsonSerializer.Deserialize<AuthenticatorAssertionRawResponse>(token,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
var jsonOptions = provider.MetaData["login"].ToString();
var options = AssertionOptions.FromJson(jsonOptions);
var webAuthCred = keys.Find(k => k.Item2.Descriptor.Id.SequenceEqual(clientResponse.Id));
if (webAuthCred == null)
{
return false;
}
// Callback to check user ownership of credential. Always return true since we have already
// established ownership in this context.
IsUserHandleOwnerOfCredentialIdAsync callback = (args, cancellationToken) => Task.FromResult(true);
var res = await _fido2.MakeAssertionAsync(clientResponse, options, webAuthCred.Item2.PublicKey, webAuthCred.Item2.SignatureCounter, callback);
provider.MetaData.Remove("login");
// Update SignatureCounter
webAuthCred.Item2.SignatureCounter = res.Counter;
var providers = user.GetTwoFactorProviders();
providers[TwoFactorProviderType.WebAuthn].MetaData[webAuthCred.Item1] = webAuthCred.Item2;
user.SetTwoFactorProviders(providers);
await userService.UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.WebAuthn, logEvent: false);
return res.Status == "ok";
}
private bool HasProperMetaData(TwoFactorProvider provider)
{
return provider?.MetaData?.Any() ?? false;
}
private List<Tuple<string, TwoFactorProvider.WebAuthnData>> LoadKeys(TwoFactorProvider provider)
{
var keys = new List<Tuple<string, TwoFactorProvider.WebAuthnData>>();
if (!HasProperMetaData(provider))
{
return keys;
}
// Support up to 5 keys
for (var i = 1; i <= 5; i++)
{
var keyName = $"Key{i}";
if (provider.MetaData.ContainsKey(keyName))
{
var key = new TwoFactorProvider.WebAuthnData((dynamic)provider.MetaData[keyName]);
keys.Add(new Tuple<string, TwoFactorProvider.WebAuthnData>(keyName, key));
}
}
return keys;
}
}

View File

@@ -1,75 +0,0 @@
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Settings;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.DependencyInjection;
using YubicoDotNetClient;
namespace Bit.Core.Identity;
public class YubicoOtpTokenProvider : IUserTwoFactorTokenProvider<User>
{
private readonly IServiceProvider _serviceProvider;
private readonly GlobalSettings _globalSettings;
public YubicoOtpTokenProvider(
IServiceProvider serviceProvider,
GlobalSettings globalSettings)
{
_serviceProvider = serviceProvider;
_globalSettings = globalSettings;
}
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
if (!(await userService.CanAccessPremium(user)))
{
return false;
}
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.YubiKey);
if (!provider?.MetaData.Values.Any(v => !string.IsNullOrWhiteSpace((string)v)) ?? true)
{
return false;
}
return await userService.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.YubiKey, user);
}
public Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
{
return Task.FromResult<string>(null);
}
public async Task<bool> ValidateAsync(string purpose, string token, UserManager<User> manager, User user)
{
var userService = _serviceProvider.GetRequiredService<IUserService>();
if (!(await userService.CanAccessPremium(user)))
{
return false;
}
if (string.IsNullOrWhiteSpace(token) || token.Length < 32 || token.Length > 48)
{
return false;
}
var id = token.Substring(0, 12);
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.YubiKey);
if (!provider.MetaData.ContainsValue(id))
{
return false;
}
var client = new YubicoClient(_globalSettings.Yubico.ClientId, _globalSettings.Yubico.Key);
if (_globalSettings.Yubico.ValidationUrls != null && _globalSettings.Yubico.ValidationUrls.Length > 0)
{
client.SetUrls(_globalSettings.Yubico.ValidationUrls);
}
var response = await client.VerifyAsync(token);
return response.Status == YubicoResponseStatus.Ok;
}
}