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:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using Bit.Core.Identity;
|
||||
using Bit.Core.Auth.Identity;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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]}";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{ }
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user