Files
server/src/Core/Services/Implementations/UserService.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1490 lines
52 KiB
C#
Raw Normal View History

using System.Security.Claims;
using System.Text.Json;
[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
2023-04-14 13:25:56 -04:00
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
2017-07-01 23:20:19 -04:00
using Bit.Core.Exceptions;
using Bit.Core.Models.Business;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Core.Tools.Entities;
using Bit.Core.Tools.Enums;
using Bit.Core.Tools.Models.Business;
using Bit.Core.Tools.Services;
using Bit.Core.Utilities;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Repositories;
2021-03-22 23:21:43 +01:00
using Fido2NetLib;
using Fido2NetLib.Objects;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using File = System.IO.File;
2015-12-08 22:57:38 -05:00
namespace Bit.Core.Services;
public class UserService : UserManager<User>, IUserService, IDisposable
2022-08-29 16:06:55 -04:00
{
2017-07-06 14:55:58 -04:00
private const string PremiumPlanId = "premium-annually";
private const string StoragePlanId = "storage-gb-annually";
2022-08-29 16:06:55 -04:00
2015-12-08 22:57:38 -05:00
private readonly IUserRepository _userRepository;
private readonly ICipherRepository _cipherRepository;
private readonly IOrganizationUserRepository _organizationUserRepository;
2018-12-03 10:56:55 -05:00
private readonly IOrganizationRepository _organizationRepository;
2015-12-08 22:57:38 -05:00
private readonly IMailService _mailService;
2017-05-26 09:44:54 -04:00
private readonly IPushNotificationService _pushService;
2015-12-08 22:57:38 -05:00
private readonly IdentityErrorDescriber _identityErrorDescriber;
private readonly IdentityOptions _identityOptions;
private readonly IPasswordHasher<User> _passwordHasher;
private readonly IEnumerable<IPasswordValidator<User>> _passwordValidators;
2017-08-11 22:55:25 -04:00
private readonly ILicensingService _licenseService;
2017-12-01 10:07:14 -05:00
private readonly IEventService _eventService;
private readonly IApplicationCacheService _applicationCacheService;
2019-02-08 23:53:09 -05:00
private readonly IPaymentService _paymentService;
2020-02-19 14:56:16 -05:00
private readonly IPolicyRepository _policyRepository;
[EC-787] Create a method in PolicyService to check if a policy applies to a user (#2537) * [EC-787] Add new stored procedure OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Add new method IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Add OrganizationUserPolicyDetails to represent policies applicable to a specific user * [EC-787] Add method IPolicyService.GetPoliciesApplicableToUser to filter the obtained policy data * [EC-787] Returning PolicyData on stored procedures * [EC-787] Changed GetPoliciesApplicableToUserAsync to return ICollection * [EC-787] Switched all usings of IPolicyRepository.GetManyByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Removed policy logic from BaseRequestValidator and added usage of IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for OrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Changed integration test to check for single result * [EC-787] Marked IPolicyRepository methods GetManyByTypeApplicableToUserIdAsync and GetCountByTypeApplicableToUserIdAsync as obsolete * [EC-787] Returning OrganizationUserId on OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Remove deprecated stored procedures Policy_CountByTypeApplicableToUser, Policy_ReadByTypeApplicableToUser and function PolicyApplicableToUser * [EC-787] Added method IPolicyService.AnyPoliciesApplicableToUserAsync * [EC-787] Removed 'OrganizationUserType' parameter from queries * [EC-787] Formatted OrganizationUserPolicyDetailsCompare * [EC-787] Renamed SQL migration files * [EC-787] Changed OrganizationUser_ReadByUserIdWithPolicyDetails to return Permissions json * [EC-787] Refactored excluded user types for each Policy * [EC-787] Updated dates on dbo_future files * [EC-787] Remove dbo_future files from sql proj * [EC-787] Added parameter PolicyType to IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Rewrote OrganizationUser_ReadByUserIdWithPolicyDetails and added parameter for PolicyType * Update util/Migrator/DbScripts/2023-03-10_00_OrganizationUserReadByUserIdWithPolicyDetails.sql Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2023-05-12 08:22:19 +01:00
private readonly IPolicyService _policyService;
private readonly IDataProtector _organizationServiceDataProtector;
private readonly IReferenceEventService _referenceEventService;
2021-03-22 23:21:43 +01:00
private readonly IFido2 _fido2;
private readonly ICurrentContext _currentContext;
private readonly IGlobalSettings _globalSettings;
private readonly IOrganizationService _organizationService;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IStripeSyncService _stripeSyncService;
2022-08-29 16:06:55 -04:00
2015-12-08 22:57:38 -05:00
public UserService(
IUserRepository userRepository,
ICipherRepository cipherRepository,
IOrganizationUserRepository organizationUserRepository,
2018-12-03 10:56:55 -05:00
IOrganizationRepository organizationRepository,
2015-12-08 22:57:38 -05:00
IMailService mailService,
2017-05-26 09:44:54 -04:00
IPushNotificationService pushService,
2015-12-08 22:57:38 -05:00
IUserStore<User> store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<User> passwordHasher,
IEnumerable<IUserValidator<User>> userValidators,
IEnumerable<IPasswordValidator<User>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
ILogger<UserManager<User>> logger,
2017-08-11 22:55:25 -04:00
ILicensingService licenseService,
2017-12-01 10:07:14 -05:00
IEventService eventService,
IApplicationCacheService applicationCacheService,
IDataProtectionProvider dataProtectionProvider,
2019-02-08 23:53:09 -05:00
IPaymentService paymentService,
2020-02-19 14:56:16 -05:00
IPolicyRepository policyRepository,
[EC-787] Create a method in PolicyService to check if a policy applies to a user (#2537) * [EC-787] Add new stored procedure OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Add new method IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Add OrganizationUserPolicyDetails to represent policies applicable to a specific user * [EC-787] Add method IPolicyService.GetPoliciesApplicableToUser to filter the obtained policy data * [EC-787] Returning PolicyData on stored procedures * [EC-787] Changed GetPoliciesApplicableToUserAsync to return ICollection * [EC-787] Switched all usings of IPolicyRepository.GetManyByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Removed policy logic from BaseRequestValidator and added usage of IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for OrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Changed integration test to check for single result * [EC-787] Marked IPolicyRepository methods GetManyByTypeApplicableToUserIdAsync and GetCountByTypeApplicableToUserIdAsync as obsolete * [EC-787] Returning OrganizationUserId on OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Remove deprecated stored procedures Policy_CountByTypeApplicableToUser, Policy_ReadByTypeApplicableToUser and function PolicyApplicableToUser * [EC-787] Added method IPolicyService.AnyPoliciesApplicableToUserAsync * [EC-787] Removed 'OrganizationUserType' parameter from queries * [EC-787] Formatted OrganizationUserPolicyDetailsCompare * [EC-787] Renamed SQL migration files * [EC-787] Changed OrganizationUser_ReadByUserIdWithPolicyDetails to return Permissions json * [EC-787] Refactored excluded user types for each Policy * [EC-787] Updated dates on dbo_future files * [EC-787] Remove dbo_future files from sql proj * [EC-787] Added parameter PolicyType to IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Rewrote OrganizationUser_ReadByUserIdWithPolicyDetails and added parameter for PolicyType * Update util/Migrator/DbScripts/2023-03-10_00_OrganizationUserReadByUserIdWithPolicyDetails.sql Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2023-05-12 08:22:19 +01:00
IPolicyService policyService,
IReferenceEventService referenceEventService,
2021-03-22 23:21:43 +01:00
IFido2 fido2,
ICurrentContext currentContext,
IGlobalSettings globalSettings,
IOrganizationService organizationService,
IProviderUserRepository providerUserRepository,
IStripeSyncService stripeSyncService)
2022-08-29 16:06:55 -04:00
: base(
store,
2015-12-08 22:57:38 -05:00
optionsAccessor,
passwordHasher,
userValidators,
passwordValidators,
keyNormalizer,
2022-08-29 16:06:55 -04:00
errors,
2015-12-08 22:57:38 -05:00
services,
2022-08-29 16:06:55 -04:00
logger)
{
2015-12-08 22:57:38 -05:00
_userRepository = userRepository;
_cipherRepository = cipherRepository;
_organizationUserRepository = organizationUserRepository;
2018-12-03 10:56:55 -05:00
_organizationRepository = organizationRepository;
2015-12-08 22:57:38 -05:00
_mailService = mailService;
_pushService = pushService;
2015-12-08 22:57:38 -05:00
_identityOptions = optionsAccessor?.Value ?? new IdentityOptions();
_identityErrorDescriber = errors;
_passwordHasher = passwordHasher;
_passwordValidators = passwordValidators;
2017-08-11 22:55:25 -04:00
_licenseService = licenseService;
2017-12-01 10:07:14 -05:00
_eventService = eventService;
_applicationCacheService = applicationCacheService;
2019-02-08 23:53:09 -05:00
_paymentService = paymentService;
2020-02-19 14:56:16 -05:00
_policyRepository = policyRepository;
[EC-787] Create a method in PolicyService to check if a policy applies to a user (#2537) * [EC-787] Add new stored procedure OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Add new method IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Add OrganizationUserPolicyDetails to represent policies applicable to a specific user * [EC-787] Add method IPolicyService.GetPoliciesApplicableToUser to filter the obtained policy data * [EC-787] Returning PolicyData on stored procedures * [EC-787] Changed GetPoliciesApplicableToUserAsync to return ICollection * [EC-787] Switched all usings of IPolicyRepository.GetManyByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Removed policy logic from BaseRequestValidator and added usage of IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for OrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Changed integration test to check for single result * [EC-787] Marked IPolicyRepository methods GetManyByTypeApplicableToUserIdAsync and GetCountByTypeApplicableToUserIdAsync as obsolete * [EC-787] Returning OrganizationUserId on OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Remove deprecated stored procedures Policy_CountByTypeApplicableToUser, Policy_ReadByTypeApplicableToUser and function PolicyApplicableToUser * [EC-787] Added method IPolicyService.AnyPoliciesApplicableToUserAsync * [EC-787] Removed 'OrganizationUserType' parameter from queries * [EC-787] Formatted OrganizationUserPolicyDetailsCompare * [EC-787] Renamed SQL migration files * [EC-787] Changed OrganizationUser_ReadByUserIdWithPolicyDetails to return Permissions json * [EC-787] Refactored excluded user types for each Policy * [EC-787] Updated dates on dbo_future files * [EC-787] Remove dbo_future files from sql proj * [EC-787] Added parameter PolicyType to IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Rewrote OrganizationUser_ReadByUserIdWithPolicyDetails and added parameter for PolicyType * Update util/Migrator/DbScripts/2023-03-10_00_OrganizationUserReadByUserIdWithPolicyDetails.sql Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2023-05-12 08:22:19 +01:00
_policyService = policyService;
_organizationServiceDataProtector = dataProtectionProvider.CreateProtector(
"OrganizationServiceDataProtector");
_referenceEventService = referenceEventService;
2021-03-22 23:21:43 +01:00
_fido2 = fido2;
_currentContext = currentContext;
2017-06-21 22:33:45 -04:00
_globalSettings = globalSettings;
_organizationService = organizationService;
_providerUserRepository = providerUserRepository;
_stripeSyncService = stripeSyncService;
}
public Guid? GetProperUserId(ClaimsPrincipal principal)
2022-08-29 16:06:55 -04:00
{
if (!Guid.TryParse(GetUserId(principal), out var userIdGuid))
2022-08-29 14:53:16 -04:00
{
return null;
}
return userIdGuid;
}
public async Task<User> GetUserByIdAsync(string userId)
2022-08-29 16:06:55 -04:00
{
if (_currentContext?.User != null &&
string.Equals(_currentContext.User.Id.ToString(), userId, StringComparison.InvariantCultureIgnoreCase))
2022-08-29 16:06:55 -04:00
{
return _currentContext.User;
}
if (!Guid.TryParse(userId, out var userIdGuid))
2015-12-08 22:57:38 -05:00
{
return null;
}
_currentContext.User = await _userRepository.GetByIdAsync(userIdGuid);
return _currentContext.User;
2022-08-29 14:53:16 -04:00
}
public async Task<User> GetUserByIdAsync(Guid userId)
2022-08-29 16:06:55 -04:00
{
if (_currentContext?.User != null && _currentContext.User.Id == userId)
2022-08-29 16:06:55 -04:00
{
return _currentContext.User;
2022-08-29 14:53:16 -04:00
}
2017-08-14 13:06:44 -04:00
_currentContext.User = await _userRepository.GetByIdAsync(userId);
return _currentContext.User;
2022-08-29 16:06:55 -04:00
}
2017-08-14 13:06:44 -04:00
public async Task<User> GetUserByPrincipalAsync(ClaimsPrincipal principal)
2022-08-29 16:06:55 -04:00
{
2017-08-14 13:06:44 -04:00
var userId = GetProperUserId(principal);
if (!userId.HasValue)
2015-12-08 22:57:38 -05:00
{
return null;
}
return await GetUserByIdAsync(userId.Value);
2022-08-29 16:06:55 -04:00
}
public async Task<DateTime> GetAccountRevisionDateByIdAsync(Guid userId)
2022-08-29 16:06:55 -04:00
{
2015-12-08 22:57:38 -05:00
return await _userRepository.GetAccountRevisionDateAsync(userId);
2022-08-29 16:06:55 -04:00
}
2015-12-08 22:57:38 -05:00
public async Task SaveUserAsync(User user, bool push = false)
2022-08-29 16:06:55 -04:00
{
if (user.Id == default(Guid))
2017-08-14 13:06:44 -04:00
{
2015-12-08 22:57:38 -05:00
throw new ApplicationException("Use register method to create a new user.");
}
// if the name is empty, set it to null
if (String.Equals(user.Name, String.Empty))
{
user.Name = null;
}
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
2017-05-31 09:54:32 -04:00
await _userRepository.ReplaceAsync(user);
if (push)
{
2017-08-14 13:06:44 -04:00
// push
await _pushService.PushSyncSettingsAsync(user.Id);
2015-12-08 22:57:38 -05:00
}
2022-08-29 16:06:55 -04:00
}
2015-12-08 22:57:38 -05:00
public override async Task<IdentityResult> DeleteAsync(User user)
2022-08-29 16:06:55 -04:00
{
// Check if user is the only owner of any organizations.
var onlyOwnerCount = await _organizationUserRepository.GetCountByOnlyOwnerAsync(user.Id);
if (onlyOwnerCount > 0)
{
var deletedOrg = false;
var orgs = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id,
OrganizationUserStatusType.Confirmed);
if (orgs.Count == 1)
{
2018-12-03 10:56:55 -05:00
var org = await _organizationRepository.GetByIdAsync(orgs.First().OrganizationId);
if (org != null && (!org.Enabled || string.IsNullOrWhiteSpace(org.GatewaySubscriptionId)))
{
2018-12-03 10:56:55 -05:00
var orgCount = await _organizationUserRepository.GetCountByOrganizationIdAsync(org.Id);
if (orgCount <= 1)
2018-12-03 10:56:55 -05:00
{
await _organizationRepository.DeleteAsync(org);
deletedOrg = true;
}
}
}
if (!deletedOrg)
{
return IdentityResult.Failed(new IdentityError
{
Description = "Cannot delete this user because it is the sole owner of at least one organization. Please delete these organizations or upgrade another user.",
});
}
2022-08-29 16:06:55 -04:00
}
var onlyOwnerProviderCount = await _providerUserRepository.GetCountByOnlyOwnerAsync(user.Id);
if (onlyOwnerProviderCount > 0)
2022-08-29 16:06:55 -04:00
{
return IdentityResult.Failed(new IdentityError
{
Description = "Cannot delete this user because it is the sole owner of at least one provider. Please delete these providers or upgrade another user.",
});
}
2017-08-09 10:53:42 -04:00
if (!string.IsNullOrWhiteSpace(user.GatewaySubscriptionId))
{
try
2017-08-09 10:53:42 -04:00
{
await CancelPremiumAsync(user, null, true);
}
catch (GatewayException) { }
2022-08-29 14:53:16 -04:00
}
2017-08-09 10:53:42 -04:00
await _userRepository.DeleteAsync(user);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.DeleteAccount, user, _currentContext));
await _pushService.PushLogOutAsync(user.Id);
return IdentityResult.Success;
2022-08-29 16:06:55 -04:00
}
2017-08-09 10:53:42 -04:00
public async Task<IdentityResult> DeleteAsync(User user, string token)
2022-08-29 16:06:55 -04:00
{
2017-08-09 10:53:42 -04:00
if (!(await VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, "DeleteAccount", token)))
2022-08-29 14:53:16 -04:00
{
2017-07-01 23:20:19 -04:00
return IdentityResult.Failed(ErrorDescriber.InvalidToken());
2022-08-29 16:06:55 -04:00
}
2017-07-01 23:20:19 -04:00
return await DeleteAsync(user);
2022-08-29 14:53:16 -04:00
}
public async Task SendDeleteConfirmationAsync(string email)
2022-08-29 16:06:55 -04:00
{
2019-06-11 17:17:23 -04:00
var user = await _userRepository.GetByEmailAsync(email);
if (user == null)
2022-08-29 16:06:55 -04:00
{
2019-06-11 17:17:23 -04:00
// No user exists.
2022-08-29 16:06:55 -04:00
return;
}
var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "DeleteAccount");
2015-12-08 22:57:38 -05:00
await _mailService.SendVerifyDeleteEmailAsync(user.Email, user.Id, token);
2022-08-29 16:06:55 -04:00
}
public async Task<IdentityResult> RegisterUserAsync(User user, string masterPassword,
string token, Guid? orgUserId)
2022-08-29 16:06:55 -04:00
{
var tokenValid = false;
if (_globalSettings.DisableUserRegistration && !string.IsNullOrWhiteSpace(token) && orgUserId.HasValue)
{
tokenValid = CoreHelpers.UserInviteTokenIsValid(_organizationServiceDataProtector, token,
user.Email, orgUserId.Value, _globalSettings);
}
if (_globalSettings.DisableUserRegistration && !tokenValid)
{
throw new BadRequestException("Open registration has been disabled by the system administrator.");
}
if (orgUserId.HasValue)
2022-08-29 16:06:55 -04:00
{
var orgUser = await _organizationUserRepository.GetByIdAsync(orgUserId.Value);
if (orgUser != null)
{
var twoFactorPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgUser.OrganizationId,
PolicyType.TwoFactorAuthentication);
if (twoFactorPolicy != null && twoFactorPolicy.Enabled)
{
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
{
2015-12-08 22:57:38 -05:00
[TwoFactorProviderType.Email] = new TwoFactorProvider
{
MetaData = new Dictionary<string, object> { ["Email"] = user.Email.ToLowerInvariant() },
Enabled = true
}
});
SetTwoFactorProvider(user, TwoFactorProviderType.Email);
}
}
2022-08-29 16:06:55 -04:00
}
2020-08-13 17:30:10 -04:00
user.ApiKey = CoreHelpers.SecureRandomString(30);
var result = await base.CreateAsync(user, masterPassword);
if (result == IdentityResult.Success)
{
2015-12-08 22:57:38 -05:00
await _mailService.SendWelcomeEmailAsync(user);
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
2022-08-29 14:53:16 -04:00
}
2020-08-13 17:30:10 -04:00
return result;
2022-08-29 16:06:55 -04:00
}
2020-08-13 17:30:10 -04:00
public async Task<IdentityResult> RegisterUserAsync(User user)
2022-08-29 16:06:55 -04:00
{
2020-08-13 17:30:10 -04:00
var result = await base.CreateAsync(user);
if (result == IdentityResult.Success)
2022-08-29 16:06:55 -04:00
{
2020-08-13 17:30:10 -04:00
await _mailService.SendWelcomeEmailAsync(user);
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.Signup, user, _currentContext));
2020-08-13 17:30:10 -04:00
}
return result;
2022-08-29 16:06:55 -04:00
}
2020-08-13 17:30:10 -04:00
public async Task SendMasterPasswordHintAsync(string email)
2022-08-29 16:06:55 -04:00
{
2020-08-13 17:30:10 -04:00
var user = await _userRepository.GetByEmailAsync(email);
if (user == null)
{
// No user exists. Do we want to send an email telling them this in the future?
return;
2015-12-08 22:57:38 -05:00
}
if (string.IsNullOrWhiteSpace(user.MasterPasswordHint))
2022-08-29 16:06:55 -04:00
{
2015-12-08 22:57:38 -05:00
await _mailService.SendNoMasterPasswordHintEmailAsync(email);
return;
}
await _mailService.SendMasterPasswordHintEmailAsync(email, user.MasterPasswordHint);
2022-08-29 16:06:55 -04:00
}
public async Task SendTwoFactorEmailAsync(User user)
2022-08-29 16:06:55 -04:00
{
2015-12-08 22:57:38 -05:00
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email);
if (provider == null || provider.MetaData == null || !provider.MetaData.ContainsKey("Email"))
{
throw new ArgumentNullException("No email.");
}
var email = ((string)provider.MetaData["Email"]).ToLowerInvariant();
2015-12-08 22:57:38 -05:00
var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultEmailProvider,
"2faEmail:" + email);
await _mailService.SendTwoFactorEmailAsync(email, token);
2022-08-29 16:06:55 -04:00
}
2015-12-08 22:57:38 -05:00
public async Task<bool> VerifyTwoFactorEmailAsync(User user, string token)
2022-08-29 16:06:55 -04:00
{
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email);
if (provider == null || provider.MetaData == null || !provider.MetaData.ContainsKey("Email"))
2017-06-20 09:21:35 -04:00
{
throw new ArgumentNullException("No email.");
2017-06-20 09:21:35 -04:00
}
2017-11-02 23:29:58 -04:00
var email = ((string)provider.MetaData["Email"]).ToLowerInvariant();
return await base.VerifyUserTokenAsync(user, TokenOptions.DefaultEmailProvider,
2017-11-02 23:29:58 -04:00
"2faEmail:" + email, token);
2022-08-29 16:06:55 -04:00
}
2017-11-02 23:29:58 -04:00
public async Task<CredentialCreateOptions> StartWebAuthnRegistrationAsync(User user)
2022-08-29 16:06:55 -04:00
{
2021-03-22 23:21:43 +01:00
var providers = user.GetTwoFactorProviders();
if (providers == null)
2022-08-29 16:06:55 -04:00
{
2017-11-02 23:29:58 -04:00
providers = new Dictionary<TwoFactorProviderType, TwoFactorProvider>();
2017-06-20 09:21:35 -04:00
}
2021-03-22 23:21:43 +01:00
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
if (provider == null)
2017-06-21 22:33:45 -04:00
{
2021-03-22 23:21:43 +01:00
provider = new TwoFactorProvider
{
2021-03-22 23:21:43 +01:00
Enabled = false
};
2022-08-29 16:06:55 -04:00
}
2021-03-22 23:21:43 +01:00
if (provider.MetaData == null)
2022-08-29 16:06:55 -04:00
{
2021-03-22 23:21:43 +01:00
provider.MetaData = new Dictionary<string, object>();
}
2017-06-21 22:33:45 -04:00
var fidoUser = new Fido2User
2022-08-29 14:53:16 -04:00
{
2021-03-22 23:21:43 +01:00
DisplayName = user.Name,
Name = user.Email,
Id = user.Id.ToByteArray(),
};
2018-10-08 14:38:11 -04:00
2021-03-22 23:21:43 +01:00
var excludeCredentials = provider.MetaData
.Where(k => k.Key.StartsWith("Key"))
.Select(k => new TwoFactorProvider.WebAuthnData((dynamic)k.Value).Descriptor)
.ToList();
2018-10-08 14:38:11 -04:00
var authenticatorSelection = new AuthenticatorSelection
2022-08-29 16:06:55 -04:00
{
2021-03-22 23:21:43 +01:00
AuthenticatorAttachment = null,
RequireResidentKey = false,
2021-03-22 23:21:43 +01:00
UserVerification = UserVerificationRequirement.Discouraged
2022-08-29 16:06:55 -04:00
};
2021-03-22 23:21:43 +01:00
var options = _fido2.RequestNewCredential(fidoUser, excludeCredentials, authenticatorSelection, AttestationConveyancePreference.None);
2018-10-10 15:21:54 -04:00
2021-03-22 23:21:43 +01:00
provider.MetaData["pending"] = options.ToJson();
providers[TwoFactorProviderType.WebAuthn] = provider;
user.SetTwoFactorProviders(providers);
await UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.WebAuthn, false);
return options;
2022-08-29 16:06:55 -04:00
}
2021-03-22 23:21:43 +01:00
public async Task<bool> CompleteWebAuthRegistrationAsync(User user, int id, string name, AuthenticatorAttestationRawResponse attestationResponse)
2022-08-29 16:06:55 -04:00
{
2021-03-22 23:21:43 +01:00
var keyId = $"Key{id}";
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
if (!provider?.MetaData?.ContainsKey("pending") ?? true)
2022-08-29 14:53:16 -04:00
{
2021-03-22 23:21:43 +01:00
return false;
2022-08-29 16:06:55 -04:00
}
2021-03-22 23:21:43 +01:00
var options = CredentialCreateOptions.FromJson((string)provider.MetaData["pending"]);
// Callback to ensure credential ID is unique. Always return true since we don't care if another
// account uses the same 2FA key.
IsCredentialIdUniqueToUserAsyncDelegate callback = (args, cancellationToken) => Task.FromResult(true);
2021-03-22 23:21:43 +01:00
var success = await _fido2.MakeNewCredentialAsync(attestationResponse, options, callback);
2017-06-21 22:33:45 -04:00
2021-03-22 23:21:43 +01:00
provider.MetaData.Remove("pending");
provider.MetaData[keyId] = new TwoFactorProvider.WebAuthnData
2022-08-29 16:06:55 -04:00
{
2021-03-22 23:21:43 +01:00
Name = name,
Descriptor = new PublicKeyCredentialDescriptor(success.Result.CredentialId),
PublicKey = success.Result.PublicKey,
UserHandle = success.Result.User.Id,
SignatureCounter = success.Result.Counter,
2021-03-22 23:21:43 +01:00
CredType = success.Result.CredType,
RegDate = DateTime.Now,
AaGuid = success.Result.Aaguid
2022-08-29 16:06:55 -04:00
};
2018-10-08 14:38:11 -04:00
var providers = user.GetTwoFactorProviders();
2021-03-22 23:21:43 +01:00
providers[TwoFactorProviderType.WebAuthn] = provider;
user.SetTwoFactorProviders(providers);
2018-10-08 14:38:11 -04:00
await UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.WebAuthn);
return true;
2022-08-29 16:06:55 -04:00
}
2018-10-08 14:38:11 -04:00
2021-03-22 23:21:43 +01:00
public async Task<bool> DeleteWebAuthnKeyAsync(User user, int id)
2022-08-29 16:06:55 -04:00
{
2017-07-05 15:35:46 -04:00
var providers = user.GetTwoFactorProviders();
if (providers == null)
2022-08-29 16:06:55 -04:00
{
2021-03-22 23:21:43 +01:00
return false;
2017-06-21 22:33:45 -04:00
}
2017-07-01 23:20:19 -04:00
var keyName = $"Key{id}";
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
if (!provider?.MetaData?.ContainsKey(keyName) ?? true)
{
2017-07-05 15:35:46 -04:00
return false;
2017-07-01 23:20:19 -04:00
}
if (provider.MetaData.Count < 2)
{
2017-07-01 23:20:19 -04:00
return false;
2022-08-29 14:53:16 -04:00
}
2017-07-01 23:20:19 -04:00
provider.MetaData.Remove(keyName);
2021-03-22 23:21:43 +01:00
providers[TwoFactorProviderType.WebAuthn] = provider;
2017-07-01 23:20:19 -04:00
user.SetTwoFactorProviders(providers);
await UpdateTwoFactorProviderAsync(user, TwoFactorProviderType.WebAuthn);
return true;
}
2015-12-08 22:57:38 -05:00
public async Task SendEmailVerificationAsync(User user)
2022-08-29 16:06:55 -04:00
{
2015-12-08 22:57:38 -05:00
if (user.EmailVerified)
2022-08-29 16:06:55 -04:00
{
2015-12-08 22:57:38 -05:00
throw new BadRequestException("Email already verified.");
}
var token = await base.GenerateEmailConfirmationTokenAsync(user);
await _mailService.SendVerifyEmailEmailAsync(user.Email, user.Id, token);
2022-08-29 16:06:55 -04:00
}
2015-12-08 22:57:38 -05:00
public async Task InitiateEmailChangeAsync(User user, string newEmail)
2022-08-29 16:06:55 -04:00
{
2017-04-17 14:53:07 -04:00
var existingUser = await _userRepository.GetByEmailAsync(newEmail);
if (existingUser != null)
2022-08-29 14:53:16 -04:00
{
2015-12-08 22:57:38 -05:00
await _mailService.SendChangeEmailAlreadyExistsEmailAsync(user.Email, newEmail);
return;
}
2015-12-08 22:57:38 -05:00
var token = await base.GenerateChangeEmailTokenAsync(user, newEmail);
await _mailService.SendChangeEmailEmailAsync(newEmail, token);
2022-08-29 16:06:55 -04:00
}
2015-12-08 22:57:38 -05:00
public async Task<IdentityResult> ChangeEmailAsync(User user, string masterPassword, string newEmail,
2017-05-31 09:54:32 -04:00
string newMasterPassword, string token, string key)
2022-08-29 16:06:55 -04:00
{
2015-12-08 22:57:38 -05:00
var verifyPasswordResult = _passwordHasher.VerifyHashedPassword(user, user.MasterPassword, masterPassword);
if (verifyPasswordResult == PasswordVerificationResult.Failed)
2022-08-29 16:06:55 -04:00
{
2015-12-08 22:57:38 -05:00
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
}
2017-04-17 14:53:07 -04:00
if (!await base.VerifyUserTokenAsync(user, _identityOptions.Tokens.ChangeEmailTokenProvider,
GetChangeEmailTokenPurpose(newEmail), token))
2022-08-29 14:53:16 -04:00
{
2017-04-17 14:53:07 -04:00
return IdentityResult.Failed(_identityErrorDescriber.InvalidToken());
2022-08-29 14:53:16 -04:00
}
2017-04-17 14:53:07 -04:00
var existingUser = await _userRepository.GetByEmailAsync(newEmail);
if (existingUser != null && existingUser.Id != user.Id)
2022-08-29 16:06:55 -04:00
{
2017-04-17 14:53:07 -04:00
return IdentityResult.Failed(_identityErrorDescriber.DuplicateEmail(newEmail));
2022-08-29 14:53:16 -04:00
}
2017-05-31 09:54:32 -04:00
var previousState = new
2022-08-29 16:06:55 -04:00
{
2017-05-31 09:54:32 -04:00
Key = user.Key,
MasterPassword = user.MasterPassword,
SecurityStamp = user.SecurityStamp,
Email = user.Email
2022-08-29 16:06:55 -04:00
};
2017-05-31 09:54:32 -04:00
var result = await UpdatePasswordHash(user, newMasterPassword);
if (!result.Succeeded)
2022-08-29 16:06:55 -04:00
{
return result;
2022-08-29 16:06:55 -04:00
}
var now = DateTime.UtcNow;
2017-05-31 09:54:32 -04:00
user.Key = key;
user.Email = newEmail;
user.EmailVerified = true;
user.RevisionDate = user.AccountRevisionDate = now;
user.LastEmailChangeDate = now;
2017-05-31 09:54:32 -04:00
await _userRepository.ReplaceAsync(user);
2022-08-29 16:06:55 -04:00
2017-05-31 09:54:32 -04:00
if (user.Gateway == GatewayType.Stripe)
2015-12-08 22:57:38 -05:00
{
2022-08-29 14:53:16 -04:00
2015-12-08 22:57:38 -05:00
try
{
2015-12-08 22:57:38 -05:00
await _stripeSyncService.UpdateCustomerEmailAddress(user.GatewayCustomerId,
user.BillingEmailAddress());
}
catch (Exception ex)
2022-08-29 14:53:16 -04:00
{
2015-12-08 22:57:38 -05:00
//if sync to strip fails, update email and securityStamp to previous
user.Key = previousState.Key;
2015-12-08 22:57:38 -05:00
user.Email = previousState.Email;
2017-12-01 14:06:16 -05:00
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
2015-12-08 22:57:38 -05:00
user.MasterPassword = previousState.MasterPassword;
user.SecurityStamp = previousState.SecurityStamp;
2022-08-29 16:06:55 -04:00
2015-12-08 22:57:38 -05:00
await _userRepository.ReplaceAsync(user);
return IdentityResult.Failed(new IdentityError
2022-08-29 16:06:55 -04:00
{
2015-12-08 22:57:38 -05:00
Description = ex.Message
2022-08-29 16:06:55 -04:00
});
2022-08-29 14:53:16 -04:00
}
2022-08-29 16:06:55 -04:00
}
2022-08-29 14:53:16 -04:00
await _pushService.PushLogOutAsync(user.Id);
2022-08-29 14:53:16 -04:00
2015-12-08 22:57:38 -05:00
return IdentityResult.Success;
2022-08-29 16:06:55 -04:00
}
public override Task<IdentityResult> ChangePasswordAsync(User user, string masterPassword, string newMasterPassword)
2022-08-29 16:06:55 -04:00
{
2015-12-08 22:57:38 -05:00
throw new NotImplementedException();
2022-08-29 16:06:55 -04:00
}
public async Task<IdentityResult> ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, string passwordHint,
string key)
2022-08-29 16:06:55 -04:00
{
if (user == null)
2022-08-29 16:06:55 -04:00
{
2015-12-08 22:57:38 -05:00
throw new ArgumentNullException(nameof(user));
2022-08-29 16:06:55 -04:00
}
if (await CheckPasswordAsync(user, masterPassword))
2022-08-29 16:06:55 -04:00
{
var result = await UpdatePasswordHash(user, newMasterPassword);
if (!result.Succeeded)
{
return result;
}
2015-12-08 22:57:38 -05:00
var now = DateTime.UtcNow;
user.RevisionDate = user.AccountRevisionDate = now;
user.LastPasswordChangeDate = now;
user.Key = key;
2017-12-01 14:06:16 -05:00
user.MasterPasswordHint = passwordHint;
2017-05-31 09:54:32 -04:00
await _userRepository.ReplaceAsync(user);
await _eventService.LogUserEventAsync(user.Id, EventType.User_ChangedPassword);
await _pushService.PushLogOutAsync(user.Id, true);
2015-12-08 22:57:38 -05:00
return IdentityResult.Success;
}
Logger.LogWarning("Change password failed for user {userId}.", user.Id);
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
2022-08-29 16:06:55 -04:00
}
public async Task<IdentityResult> SetPasswordAsync(User user, string masterPassword, string key,
string orgIdentifier = null)
2022-08-29 16:06:55 -04:00
{
if (user == null)
2015-12-08 22:57:38 -05:00
{
throw new ArgumentNullException(nameof(user));
}
if (!string.IsNullOrWhiteSpace(user.MasterPassword))
2015-12-08 22:57:38 -05:00
{
Logger.LogWarning("Change password failed for user {userId} - already has password.", user.Id);
return IdentityResult.Failed(_identityErrorDescriber.UserAlreadyHasPassword());
2022-08-29 14:53:16 -04:00
}
2017-12-01 10:07:14 -05:00
2015-12-08 22:57:38 -05:00
var result = await UpdatePasswordHash(user, masterPassword, true, false);
if (!result.Succeeded)
2022-08-29 16:06:55 -04:00
{
2015-12-08 22:57:38 -05:00
return result;
2022-08-29 16:06:55 -04:00
}
2017-05-31 09:54:32 -04:00
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
user.Key = key;
2017-05-31 09:54:32 -04:00
await _userRepository.ReplaceAsync(user);
await _eventService.LogUserEventAsync(user.Id, EventType.User_ChangedPassword);
2020-08-13 17:30:10 -04:00
if (!string.IsNullOrWhiteSpace(orgIdentifier))
{
await _organizationService.AcceptUserAsync(orgIdentifier, user, this);
}
return IdentityResult.Success;
}
2020-08-13 17:30:10 -04:00
public async Task<IdentityResult> SetKeyConnectorKeyAsync(User user, string key, string orgIdentifier)
{
var identityResult = CheckCanUseKeyConnector(user);
if (identityResult != null)
2022-08-29 16:06:55 -04:00
{
return identityResult;
}
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
user.Key = key;
user.UsesKeyConnector = true;
await _userRepository.ReplaceAsync(user);
await _eventService.LogUserEventAsync(user.Id, EventType.User_MigratedKeyToKeyConnector);
await _organizationService.AcceptUserAsync(orgIdentifier, user, this);
return IdentityResult.Success;
}
public async Task<IdentityResult> ConvertToKeyConnectorAsync(User user)
2022-08-29 16:06:55 -04:00
{
var identityResult = CheckCanUseKeyConnector(user);
if (identityResult != null)
2022-08-29 16:06:55 -04:00
{
return identityResult;
}
2021-10-25 15:09:14 +02:00
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
user.MasterPassword = null;
user.UsesKeyConnector = true;
2021-10-25 15:09:14 +02:00
await _userRepository.ReplaceAsync(user);
await _eventService.LogUserEventAsync(user.Id, EventType.User_MigratedKeyToKeyConnector);
2021-10-25 15:09:14 +02:00
return IdentityResult.Success;
2022-08-29 16:06:55 -04:00
}
2021-10-25 15:09:14 +02:00
private IdentityResult CheckCanUseKeyConnector(User user)
2022-08-29 16:06:55 -04:00
{
2021-10-25 15:09:14 +02:00
if (user == null)
2022-08-29 16:06:55 -04:00
{
2021-10-25 15:09:14 +02:00
throw new ArgumentNullException(nameof(user));
2022-08-29 16:06:55 -04:00
}
2021-10-25 15:09:14 +02:00
2021-11-17 11:46:35 +01:00
if (user.UsesKeyConnector)
2022-08-29 16:06:55 -04:00
{
2021-11-17 11:46:35 +01:00
Logger.LogWarning("Already uses Key Connector.");
2021-10-25 15:09:14 +02:00
return IdentityResult.Failed(_identityErrorDescriber.UserAlreadyHasPassword());
}
if (_currentContext.Organizations.Any(u =>
u.Type is OrganizationUserType.Owner or OrganizationUserType.Admin))
{
throw new BadRequestException("Cannot use Key Connector when admin or owner of an organization.");
}
return null;
2022-08-29 16:06:55 -04:00
}
public async Task<IdentityResult> AdminResetPasswordAsync(OrganizationUserType callingUserType, Guid orgId, Guid id, string newMasterPassword, string key)
2022-08-29 16:06:55 -04:00
{
// Org must be able to use reset password
var org = await _organizationRepository.GetByIdAsync(orgId);
if (org == null || !org.UseResetPassword)
2022-08-29 16:06:55 -04:00
{
throw new BadRequestException("Organization does not allow password reset.");
2022-08-29 16:06:55 -04:00
}
// Enterprise policy must be enabled
var resetPasswordPolicy =
await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword);
if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled)
2022-08-29 16:06:55 -04:00
{
throw new BadRequestException("Organization does not have the password reset policy enabled.");
}
// Org User must be confirmed and have a ResetPasswordKey
var orgUser = await _organizationUserRepository.GetByIdAsync(id);
if (orgUser == null || orgUser.Status != OrganizationUserStatusType.Confirmed ||
orgUser.OrganizationId != orgId || string.IsNullOrEmpty(orgUser.ResetPasswordKey) ||
!orgUser.UserId.HasValue)
{
throw new BadRequestException("Organization User not valid");
}
2021-11-17 11:46:35 +01:00
// Calling User must be of higher/equal user type to reset user's password
var canAdjustPassword = false;
switch (callingUserType)
2022-08-29 16:06:55 -04:00
{
case OrganizationUserType.Owner:
canAdjustPassword = true;
break;
case OrganizationUserType.Admin:
canAdjustPassword = orgUser.Type != OrganizationUserType.Owner;
break;
case OrganizationUserType.Custom:
canAdjustPassword = orgUser.Type != OrganizationUserType.Owner &&
orgUser.Type != OrganizationUserType.Admin;
break;
}
if (!canAdjustPassword)
{
throw new BadRequestException("Calling user does not have permission to reset this user's master password");
}
var user = await GetUserByIdAsync(orgUser.UserId.Value);
if (user == null)
2022-08-29 16:06:55 -04:00
{
throw new NotFoundException();
}
if (user.UsesKeyConnector)
{
throw new BadRequestException("Cannot reset password of a user with Key Connector.");
}
var result = await UpdatePasswordHash(user, newMasterPassword);
if (!result.Succeeded)
{
return result;
}
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
user.Key = key;
user.ForcePasswordReset = true;
await _userRepository.ReplaceAsync(user);
await _mailService.SendAdminResetPasswordEmailAsync(user.Email, user.Name, org.Name);
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_AdminResetPassword);
await _pushService.PushLogOutAsync(user.Id);
return IdentityResult.Success;
}
public async Task<IdentityResult> UpdateTempPasswordAsync(User user, string newMasterPassword, string key, string hint)
2022-08-29 16:06:55 -04:00
{
if (!user.ForcePasswordReset)
{
throw new BadRequestException("User does not have a temporary password to update.");
}
2021-11-17 11:46:35 +01:00
var result = await UpdatePasswordHash(user, newMasterPassword);
if (!result.Succeeded)
2022-08-29 16:06:55 -04:00
{
2021-11-17 11:46:35 +01:00
return result;
}
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
user.ForcePasswordReset = false;
user.Key = key;
user.MasterPasswordHint = hint;
await _userRepository.ReplaceAsync(user);
await _mailService.SendUpdatedTempPasswordEmailAsync(user.Email, user.Name);
await _eventService.LogUserEventAsync(user.Id, EventType.User_UpdatedTempPassword);
await _pushService.PushLogOutAsync(user.Id);
return IdentityResult.Success;
2022-08-29 16:06:55 -04:00
}
public async Task<IdentityResult> ChangeKdfAsync(User user, string masterPassword, string newMasterPassword,
string key, KdfType kdf, int kdfIterations, int? kdfMemory, int? kdfParallelism)
2022-08-29 16:06:55 -04:00
{
if (user == null)
2022-08-29 16:06:55 -04:00
{
throw new ArgumentNullException(nameof(user));
}
if (await CheckPasswordAsync(user, masterPassword))
{
var result = await UpdatePasswordHash(user, newMasterPassword);
if (!result.Succeeded)
{
return result;
}
var now = DateTime.UtcNow;
user.RevisionDate = user.AccountRevisionDate = now;
user.LastKdfChangeDate = now;
user.Key = key;
user.Kdf = kdf;
user.KdfIterations = kdfIterations;
user.KdfMemory = kdfMemory;
user.KdfParallelism = kdfParallelism;
await _userRepository.ReplaceAsync(user);
await _pushService.PushLogOutAsync(user.Id);
return IdentityResult.Success;
}
2017-05-31 09:54:32 -04:00
Logger.LogWarning("Change KDF failed for user {userId}.", user.Id);
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
}
2015-12-08 22:57:38 -05:00
public async Task<IdentityResult> UpdateKeyAsync(User user, string masterPassword, string key, string privateKey,
IEnumerable<Cipher> ciphers, IEnumerable<Folder> folders, IEnumerable<Send> sends)
2022-08-29 16:06:55 -04:00
{
if (user == null)
2015-12-08 22:57:38 -05:00
{
throw new ArgumentNullException(nameof(user));
2015-12-08 22:57:38 -05:00
}
if (await CheckPasswordAsync(user, masterPassword))
{
var now = DateTime.UtcNow;
user.RevisionDate = user.AccountRevisionDate = now;
user.LastKeyRotationDate = now;
2017-05-31 09:54:32 -04:00
user.SecurityStamp = Guid.NewGuid().ToString();
user.Key = key;
2017-04-17 14:53:07 -04:00
user.PrivateKey = privateKey;
if (ciphers.Any() || folders.Any() || sends.Any())
{
await _cipherRepository.UpdateUserKeysAndCiphersAsync(user, ciphers, folders, sends);
2022-08-29 14:53:16 -04:00
}
else
{
await _userRepository.ReplaceAsync(user);
}
Trusted Device Encryption feature (#3151) * [PM-1203] feat: allow verification for all passwordless accounts (#3038) * [PM-1033] Org invite user creation flow 1 (#3028) * [PM-1033] feat: remove user verification from password enrollment * [PM-1033] feat: auto accept invitation when enrolling into password reset * [PM-1033] fix: controller tests * [PM-1033] refactor: `UpdateUserResetPasswordEnrollmentCommand` * [PM-1033] refactor(wip): make `AcceptUserCommand` * Revert "[PM-1033] refactor(wip): make `AcceptUserCommand`" This reverts commit dc1319e7fa70c4844bbc70e0b01089b682ac2843. * Revert "[PM-1033] refactor: `UpdateUserResetPasswordEnrollmentCommand`" This reverts commit 43df689c7f244af4f7ffec1f9768a72081a624c3. * [PM-1033] refactor: move invite accept to controller This avoids creating yet another method that depends on having `IUserService` passed in as a parameter * [PM-1033] fix: add missing changes * [PM-1381] Add Trusted Device Keys to Auth Response (#3066) * Return Keys for Trusted Device - Check whether the current logging in device is trusted - Return their keys on successful login * Formatting * Address PR Feedback * Add Remarks Comment * [PM-1338] `AuthRequest` Event Logs (#3046) * Update AuthRequestController - Only allow AdminApproval Requests to be created from authed endpoint - Add endpoint that has authentication to be able to create admin approval * Add PasswordlessAuthSettings - Add settings for customizing expiration times * Add new EventTypes * Add Logic for AdminApproval Type - Add logic for validating AdminApproval expiration - Add event logging for Approval/Disapproval of AdminApproval - Add logic for creating AdminApproval types * Add Test Helpers - Change BitAutoData to allow you to use string representations of common types. * Add/Update AuthRequestService Tests * Run Formatting * Switch to 7 Days * Add Test Covering ResponseDate Being Set * Address PR Feedback - Create helper for checking if date is expired - Move validation logic into smaller methods * Switch to User Event Type - Make RequestDeviceApproval user type - User types will log for each org user is in * [PM-2998] Move Approving Device Check (#3101) * Move Check for Approving Devices - Exclude currently logging in device - Remove old way of checking - Add tests asserting behavior * Update DeviceType list * Update Naming & Address PR Feedback * Fix Tests * Address PR Feedback * Formatting * Now Fully Update Naming? * Feature/auth/pm 2759/add can reset password to user decryption options (#3113) * PM-2759 - BaseRequestValidator.cs - CreateUserDecryptionOptionsAsync - Add new hasManageResetPasswordPermission for post SSO redirect logic required on client. * PM-2759 - Update IdentityServerSsoTests.cs to all pass based on the addition of HasManageResetPasswordPermission to TrustedDeviceUserDecryptionOption * IdentityServerSsoTests.cs - fix typo in test name: LoggingApproval --> LoginApproval * PM1259 - Add test case for verifying that TrustedDeviceOption.hasManageResetPasswordPermission is set properly based on user permission * dotnet format run * Feature/auth/pm 2759/add can reset password to user decryption options fix jit users (#3120) * PM-2759 - IdentityServer - CreateUserDecryptionOptionsAsync - hasManageResetPasswordPermission set logic was broken for JIT provisioned users as I assumed we would always have a list of at least 1 org during the SSO process. Added TODO for future test addition but getting this out there now as QA is blocked by being unable to create JIT provisioned users. * dotnet format * Tiny tweak * [PM-1339] Allow Rotating Device Keys (#3096) * Allow Rotation of Trusted Device Keys - Add endpoint for getting keys relating to rotation - Add endpoint for rotating your current device - In the same endpoint allow a list of other devices to rotate * Formatting * Use Extension Method * Add Tests from PR Co-authored-by: Jared Snider <jsnider@bitwarden.com> --------- Co-authored-by: Jared Snider <jsnider@bitwarden.com> * Check the user directly if they have the ResetPasswordKey (#3153) * PM-3327 - UpdateKeyAsync must exempt the currently calling device from the logout notification in order to prevent prematurely logging the user out before the client side key rotation process can complete. The calling device will log itself out once it is done. (#3170) * Allow OTP Requests When Users Are On TDE (#3184) * [PM-3356][PM-3292] Allow OTP For All (#3188) * Allow OTP For All - On a trusted device isn't a good check because a user might be using a trusted device locally but not trusted it long term - The logic wasn't working for KC users anyways * Remove Old Comment * [AC-1601] Added RequireSso policy as a dependency of TDE (#3209) * Added RequireSso policy as a dependency of TDE. * Added test for RequireSso for TDE. * Added save. * Fixed policy name. --------- Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com> Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Co-authored-by: Jared Snider <jsnider@bitwarden.com>
2023-08-17 16:03:06 -04:00
await _pushService.PushLogOutAsync(user.Id, excludeCurrentContextFromPush: true);
return IdentityResult.Success;
}
Logger.LogWarning("Update key failed for user {userId}.", user.Id);
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
2022-08-29 16:06:55 -04:00
}
public async Task<IdentityResult> RefreshSecurityStampAsync(User user, string secret)
2022-08-29 16:06:55 -04:00
{
if (user == null)
{
2021-03-22 23:21:43 +01:00
throw new ArgumentNullException(nameof(user));
}
2020-02-19 14:56:16 -05:00
if (await VerifySecretAsync(user, secret))
{
var result = await base.UpdateSecurityStampAsync(user);
if (!result.Succeeded)
{
return result;
}
2016-02-21 01:10:31 -05:00
await SaveUserAsync(user);
2017-12-01 14:06:16 -05:00
await _pushService.PushLogOutAsync(user.Id);
return IdentityResult.Success;
2022-08-29 14:53:16 -04:00
}
Logger.LogWarning("Refresh security stamp failed for user {userId}.", user.Id);
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
2022-08-29 16:06:55 -04:00
}
public async Task UpdateTwoFactorProviderAsync(User user, TwoFactorProviderType type, bool setEnabled = true, bool logEvent = true)
2022-08-29 16:06:55 -04:00
{
2020-02-19 14:56:16 -05:00
SetTwoFactorProvider(user, type, setEnabled);
2021-03-22 23:21:43 +01:00
await SaveUserAsync(user);
2020-02-19 14:56:16 -05:00
if (logEvent)
{
await _eventService.LogUserEventAsync(user.Id, EventType.User_Updated2fa);
2022-08-29 16:06:55 -04:00
}
}
public async Task DisableTwoFactorProviderAsync(User user, TwoFactorProviderType type,
2020-02-19 14:56:16 -05:00
IOrganizationService organizationService)
2022-08-29 16:06:55 -04:00
{
var providers = user.GetTwoFactorProviders();
if (!providers?.ContainsKey(type) ?? true)
{
return;
}
providers.Remove(type);
user.SetTwoFactorProviders(providers);
2016-02-21 01:10:31 -05:00
await SaveUserAsync(user);
await _eventService.LogUserEventAsync(user.Id, EventType.User_Disabled2fa);
if (!await TwoFactorIsEnabledAsync(user))
2022-08-29 16:06:55 -04:00
{
await CheckPoliciesOnTwoFactorRemovalAsync(user, organizationService);
}
2022-08-29 16:06:55 -04:00
}
public async Task<bool> RecoverTwoFactorAsync(string email, string secret, string recoveryCode,
IOrganizationService organizationService)
2022-08-29 16:06:55 -04:00
{
var user = await _userRepository.GetByEmailAsync(email);
if (user == null)
2017-07-06 14:55:58 -04:00
{
2019-09-18 10:52:53 -04:00
// No user exists. Do we want to send an email telling them this in the future?
2017-08-11 22:55:25 -04:00
return false;
}
2017-08-11 22:55:25 -04:00
if (!await VerifySecretAsync(user, secret))
2022-08-29 16:06:55 -04:00
{
return false;
2017-07-06 14:55:58 -04:00
}
2019-09-19 08:46:26 -04:00
if (!CoreHelpers.FixedTimeEquals(user.TwoFactorRecoveryCode, recoveryCode))
{
return false;
}
2019-09-19 08:46:26 -04:00
user.TwoFactorProviders = null;
user.TwoFactorRecoveryCode = CoreHelpers.SecureRandomString(32, upper: false, special: false);
await SaveUserAsync(user);
2019-09-19 08:46:26 -04:00
await _mailService.SendRecoverTwoFactorEmail(user.Email, DateTime.UtcNow, _currentContext.IpAddress);
2017-12-01 14:06:16 -05:00
await _eventService.LogUserEventAsync(user.Id, EventType.User_Recovered2fa);
2019-09-19 08:46:26 -04:00
await CheckPoliciesOnTwoFactorRemovalAsync(user, organizationService);
2022-08-29 14:53:16 -04:00
2019-09-19 08:46:26 -04:00
return true;
2022-08-29 16:06:55 -04:00
}
2019-09-19 08:46:26 -04:00
public async Task<Tuple<bool, string>> SignUpPremiumAsync(User user, string paymentToken,
PaymentMethodType paymentMethodType, short additionalStorageGb, UserLicense license,
TaxInfo taxInfo)
2022-08-29 16:06:55 -04:00
{
2019-09-19 08:46:26 -04:00
if (user.Premium)
2022-08-29 14:53:16 -04:00
{
2019-09-19 08:46:26 -04:00
throw new BadRequestException("Already a premium user.");
}
if (additionalStorageGb < 0)
{
throw new BadRequestException("You can't subtract storage!");
}
if ((paymentMethodType == PaymentMethodType.GoogleInApp ||
paymentMethodType == PaymentMethodType.AppleInApp) && additionalStorageGb > 0)
{
throw new BadRequestException("You cannot add storage with this payment method.");
}
2019-09-18 10:52:53 -04:00
string paymentIntentClientSecret = null;
IPaymentService paymentService = null;
2019-09-18 10:52:53 -04:00
if (_globalSettings.SelfHosted)
2022-08-29 16:06:55 -04:00
{
if (license == null || !_licenseService.VerifyLicense(license))
{
throw new BadRequestException("Invalid license.");
}
if (!license.CanUse(user))
{
throw new BadRequestException("This license is not valid for this user.");
}
var dir = $"{_globalSettings.LicenseDirectory}/user";
Directory.CreateDirectory(dir);
using var fs = File.OpenWrite(Path.Combine(dir, $"{user.Id}.json"));
await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented);
2022-08-29 16:06:55 -04:00
}
else
{
2017-08-11 22:55:25 -04:00
paymentIntentClientSecret = await _paymentService.PurchasePremiumAsync(user, paymentMethodType,
paymentToken, additionalStorageGb, taxInfo);
2022-08-29 16:06:55 -04:00
}
2022-08-29 14:53:16 -04:00
user.Premium = true;
user.RevisionDate = DateTime.UtcNow;
2022-08-29 16:06:55 -04:00
if (_globalSettings.SelfHosted)
2022-08-29 16:06:55 -04:00
{
user.MaxStorageGb = 10240; // 10 TB
user.LicenseKey = license.LicenseKey;
user.PremiumExpirationDate = license.Expires;
2017-07-06 14:55:58 -04:00
}
2022-08-29 14:53:16 -04:00
else
{
user.MaxStorageGb = (short)(1 + additionalStorageGb);
user.LicenseKey = CoreHelpers.SecureRandomString(20);
}
2022-08-29 16:06:55 -04:00
try
{
await SaveUserAsync(user);
await _pushService.PushSyncVaultAsync(user.Id);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.UpgradePlan, user, _currentContext)
{
Storage = user.MaxStorageGb,
PlanName = PremiumPlanId,
});
2017-08-13 00:33:37 -04:00
}
2017-08-11 22:55:25 -04:00
catch when (!_globalSettings.SelfHosted)
2022-08-29 16:06:55 -04:00
{
2017-08-11 22:55:25 -04:00
await paymentService.CancelAndRecoverChargesAsync(user);
2022-08-29 16:06:55 -04:00
throw;
}
return new Tuple<bool, string>(string.IsNullOrWhiteSpace(paymentIntentClientSecret),
paymentIntentClientSecret);
2022-08-29 16:06:55 -04:00
}
2017-08-13 00:33:37 -04:00
public async Task IapCheckAsync(User user, PaymentMethodType paymentMethodType)
2022-08-29 16:06:55 -04:00
{
if (paymentMethodType != PaymentMethodType.AppleInApp)
2022-08-29 14:53:16 -04:00
{
throw new BadRequestException("Payment method not supported for in-app purchases.");
2017-07-06 14:55:58 -04:00
}
2018-12-31 13:34:02 -05:00
if (user.Premium)
2022-08-29 16:06:55 -04:00
{
2018-12-31 13:34:02 -05:00
throw new BadRequestException("Already a premium user.");
2017-07-06 14:55:58 -04:00
}
if (!string.IsNullOrWhiteSpace(user.GatewayCustomerId))
{
var customerService = new Stripe.CustomerService();
var customer = await customerService.GetAsync(user.GatewayCustomerId);
if (customer != null && customer.Balance != 0)
{
throw new BadRequestException("Customer balance cannot exist when using in-app purchases.");
}
2017-07-06 14:55:58 -04:00
}
2022-08-29 16:06:55 -04:00
}
2017-07-06 14:55:58 -04:00
public async Task UpdateLicenseAsync(User user, UserLicense license)
2022-08-29 16:06:55 -04:00
{
if (!_globalSettings.SelfHosted)
{
throw new InvalidOperationException("Licenses require self hosting.");
}
if (license?.LicenseType != null && license.LicenseType != LicenseType.User)
{
throw new BadRequestException("Organization licenses cannot be applied to a user. "
+ "Upload this license from the Organization settings page.");
}
2017-08-12 22:16:42 -04:00
if (license == null || !_licenseService.VerifyLicense(license))
2017-07-25 09:04:22 -04:00
{
throw new BadRequestException("Invalid license.");
}
if (!license.CanUse(user))
{
2017-08-12 22:16:42 -04:00
throw new BadRequestException("This license is not valid for this user.");
}
var dir = $"{_globalSettings.LicenseDirectory}/user";
Directory.CreateDirectory(dir);
2017-08-12 22:16:42 -04:00
using var fs = File.OpenWrite(Path.Combine(dir, $"{user.Id}.json"));
await JsonSerializer.SerializeAsync(fs, license, JsonHelpers.Indented);
2022-08-29 16:06:55 -04:00
user.Premium = license.Premium;
2017-08-12 22:16:42 -04:00
user.RevisionDate = DateTime.UtcNow;
user.MaxStorageGb = _globalSettings.SelfHosted ? 10240 : license.MaxStorageGb; // 10 TB
user.LicenseKey = license.LicenseKey;
2017-08-12 22:16:42 -04:00
user.PremiumExpirationDate = license.Expires;
await SaveUserAsync(user);
2022-08-29 16:06:55 -04:00
}
2017-08-12 22:16:42 -04:00
public async Task<string> AdjustStorageAsync(User user, short storageAdjustmentGb)
2022-08-29 16:06:55 -04:00
{
if (user == null)
2017-08-12 22:16:42 -04:00
{
throw new ArgumentNullException(nameof(user));
}
2019-02-18 15:40:47 -05:00
if (!user.Premium)
2022-08-29 14:53:16 -04:00
{
2019-02-18 15:40:47 -05:00
throw new BadRequestException("Not a premium user.");
}
2018-04-17 08:10:17 -04:00
var secret = await BillingHelpers.AdjustStorageAsync(_paymentService, user, storageAdjustmentGb,
StoragePlanId);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.AdjustStorage, user, _currentContext)
2018-04-17 08:10:17 -04:00
{
Storage = storageAdjustmentGb,
PlanName = StoragePlanId,
});
2017-08-11 22:55:25 -04:00
await SaveUserAsync(user);
return secret;
2018-04-17 08:10:17 -04:00
}
public async Task ReplacePaymentMethodAsync(User user, string paymentToken, PaymentMethodType paymentMethodType, TaxInfo taxInfo)
2022-08-29 16:06:55 -04:00
{
2018-04-17 08:10:17 -04:00
if (paymentToken.StartsWith("btok_"))
2022-08-29 16:06:55 -04:00
{
2018-04-17 08:10:17 -04:00
throw new BadRequestException("Invalid token.");
}
var updated = await _paymentService.UpdatePaymentMethodAsync(user, paymentMethodType, paymentToken, taxInfo: taxInfo);
if (updated)
2022-08-29 16:06:55 -04:00
{
2018-04-17 08:10:17 -04:00
await SaveUserAsync(user);
2022-08-29 14:53:16 -04:00
}
2022-08-29 16:06:55 -04:00
}
2022-08-29 14:53:16 -04:00
2018-04-17 08:10:17 -04:00
public async Task CancelPremiumAsync(User user, bool? endOfPeriod = null, bool accountDelete = false)
2022-08-29 16:06:55 -04:00
{
2018-04-17 08:10:17 -04:00
var eop = endOfPeriod.GetValueOrDefault(true);
if (!endOfPeriod.HasValue && user.PremiumExpirationDate.HasValue &&
user.PremiumExpirationDate.Value < DateTime.UtcNow)
2022-08-29 14:53:16 -04:00
{
eop = false;
2022-08-29 16:06:55 -04:00
}
await _paymentService.CancelSubscriptionAsync(user, eop, accountDelete);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.CancelSubscription, user, _currentContext)
{
EndOfPeriod = eop,
2018-04-17 08:10:17 -04:00
});
2022-08-29 14:53:16 -04:00
}
2017-08-12 22:16:42 -04:00
public async Task ReinstatePremiumAsync(User user)
{
await _paymentService.ReinstateSubscriptionAsync(user);
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.ReinstateSubscription, user, _currentContext));
2022-08-29 14:53:16 -04:00
}
2018-04-17 08:10:17 -04:00
public async Task EnablePremiumAsync(Guid userId, DateTime? expirationDate)
{
var user = await _userRepository.GetByIdAsync(userId);
await EnablePremiumAsync(user, expirationDate);
2022-08-29 16:06:55 -04:00
}
public async Task EnablePremiumAsync(User user, DateTime? expirationDate)
{
if (user != null && !user.Premium && user.Gateway.HasValue)
2022-08-29 16:06:55 -04:00
{
2017-07-25 09:04:22 -04:00
user.Premium = true;
user.PremiumExpirationDate = expirationDate;
user.RevisionDate = DateTime.UtcNow;
await _userRepository.ReplaceAsync(user);
2022-08-29 14:53:16 -04:00
}
2022-08-29 16:06:55 -04:00
}
public async Task DisablePremiumAsync(Guid userId, DateTime? expirationDate)
2022-08-29 16:06:55 -04:00
{
var user = await _userRepository.GetByIdAsync(userId);
2017-08-12 22:16:42 -04:00
await DisablePremiumAsync(user, expirationDate);
2022-08-29 16:06:55 -04:00
}
public async Task DisablePremiumAsync(User user, DateTime? expirationDate)
2022-08-29 16:06:55 -04:00
{
if (user != null && user.Premium)
2022-08-29 14:53:16 -04:00
{
user.Premium = false;
user.PremiumExpirationDate = expirationDate;
user.RevisionDate = DateTime.UtcNow;
await _userRepository.ReplaceAsync(user);
2022-08-29 16:06:55 -04:00
}
2022-08-29 14:53:16 -04:00
}
public async Task UpdatePremiumExpirationAsync(Guid userId, DateTime? expirationDate)
2022-08-29 16:06:55 -04:00
{
var user = await _userRepository.GetByIdAsync(userId);
if (user != null)
2022-08-29 16:06:55 -04:00
{
user.PremiumExpirationDate = expirationDate;
user.RevisionDate = DateTime.UtcNow;
await _userRepository.ReplaceAsync(user);
}
2022-08-29 16:06:55 -04:00
}
public async Task<UserLicense> GenerateLicenseAsync(User user, SubscriptionInfo subscriptionInfo = null,
int? version = null)
2022-08-29 16:06:55 -04:00
{
if (user == null)
2022-08-29 14:53:16 -04:00
{
throw new NotFoundException();
2022-08-29 14:53:16 -04:00
}
if (subscriptionInfo == null && user.Gateway != null)
2022-08-29 16:06:55 -04:00
{
subscriptionInfo = await _paymentService.GetSubscriptionAsync(user);
2022-08-29 16:06:55 -04:00
}
return subscriptionInfo == null ? new UserLicense(user, _licenseService) :
new UserLicense(user, subscriptionInfo, _licenseService);
}
public override async Task<bool> CheckPasswordAsync(User user, string password)
2022-08-29 16:06:55 -04:00
{
if (user == null)
2022-08-29 16:06:55 -04:00
{
return false;
}
var result = await base.VerifyPasswordAsync(Store as IUserPasswordStore<User>, user, password);
if (result == PasswordVerificationResult.SuccessRehashNeeded)
2022-08-29 14:53:16 -04:00
{
await UpdatePasswordHash(user, password, false, false);
user.RevisionDate = DateTime.UtcNow;
await _userRepository.ReplaceAsync(user);
2022-08-29 14:53:16 -04:00
}
var success = result != PasswordVerificationResult.Failed;
if (!success)
2022-08-29 16:06:55 -04:00
{
Logger.LogWarning(0, "Invalid password for user {userId}.", user.Id);
2022-08-29 16:06:55 -04:00
}
return success;
2022-08-29 16:06:55 -04:00
}
public async Task<bool> CanAccessPremium(ITwoFactorProvidersUser user)
{
var userId = user.GetUserId();
if (!userId.HasValue)
{
return false;
}
return user.GetPremium() || await this.HasPremiumFromOrganization(user);
2022-08-29 16:06:55 -04:00
}
public async Task<bool> HasPremiumFromOrganization(ITwoFactorProvidersUser user)
2022-08-29 16:06:55 -04:00
{
var userId = user.GetUserId();
if (!userId.HasValue)
{
return false;
}
// orgUsers in the Invited status are not associated with a userId yet, so this will get
// orgUsers in Accepted and Confirmed states only
var orgUsers = await _organizationUserRepository.GetManyByUserAsync(userId.Value);
if (!orgUsers.Any())
2022-08-29 16:06:55 -04:00
{
return false;
}
var orgAbilities = await _applicationCacheService.GetOrganizationAbilitiesAsync();
return orgUsers.Any(ou =>
orgAbilities.TryGetValue(ou.OrganizationId, out var orgAbility) &&
orgAbility.UsersGetPremium &&
orgAbility.Enabled);
2022-08-29 16:06:55 -04:00
}
public async Task<bool> TwoFactorIsEnabledAsync(ITwoFactorProvidersUser user)
2022-08-29 16:06:55 -04:00
{
var providers = user.GetTwoFactorProviders();
if (providers == null)
{
return false;
}
2018-04-17 08:10:17 -04:00
foreach (var p in providers)
2015-12-08 22:57:38 -05:00
{
if (p.Value?.Enabled ?? false)
2015-12-08 22:57:38 -05:00
{
if (!TwoFactorProvider.RequiresPremium(p.Key))
2022-08-29 14:53:16 -04:00
{
return true;
2022-08-29 16:06:55 -04:00
}
if (await CanAccessPremium(user))
2022-08-29 16:06:55 -04:00
{
return true;
2015-12-08 22:57:38 -05:00
}
}
2022-08-29 16:06:55 -04:00
}
return false;
2022-08-29 16:06:55 -04:00
}
2015-12-08 22:57:38 -05:00
public async Task<bool> TwoFactorProviderIsEnabledAsync(TwoFactorProviderType provider, ITwoFactorProvidersUser user)
2018-04-17 08:10:17 -04:00
{
var providers = user.GetTwoFactorProviders();
2015-12-08 22:57:38 -05:00
if (providers == null || !providers.ContainsKey(provider) || !providers[provider].Enabled)
2022-08-29 16:06:55 -04:00
{
2015-12-08 22:57:38 -05:00
return false;
}
2021-03-22 23:21:43 +01:00
if (!TwoFactorProvider.RequiresPremium(provider))
{
return true;
}
return await CanAccessPremium(user);
}
public async Task<string> GenerateSignInTokenAsync(User user, string purpose)
2022-08-29 16:06:55 -04:00
{
var token = await GenerateUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider,
purpose);
return token;
2022-08-29 14:53:16 -04:00
}
private async Task<IdentityResult> UpdatePasswordHash(User user, string newPassword,
bool validatePassword = true, bool refreshStamp = true)
2022-08-29 16:06:55 -04:00
{
if (validatePassword)
2022-08-29 14:53:16 -04:00
{
var validate = await ValidatePasswordInternal(user, newPassword);
if (!validate.Succeeded)
{
return validate;
2022-08-29 14:53:16 -04:00
}
2022-08-29 16:06:55 -04:00
}
user.MasterPassword = _passwordHasher.HashPassword(user, newPassword);
2015-12-08 22:57:38 -05:00
if (refreshStamp)
2022-08-29 16:06:55 -04:00
{
user.SecurityStamp = Guid.NewGuid().ToString();
2022-08-29 16:06:55 -04:00
}
2022-08-29 14:53:16 -04:00
return IdentityResult.Success;
2022-08-29 16:06:55 -04:00
}
private async Task<IdentityResult> ValidatePasswordInternal(User user, string password)
2022-08-29 16:06:55 -04:00
{
var errors = new List<IdentityError>();
foreach (var v in _passwordValidators)
2022-08-29 16:06:55 -04:00
{
var result = await v.ValidateAsync(this, user, password);
if (!result.Succeeded)
{
errors.AddRange(result.Errors);
}
}
if (errors.Count > 0)
{
Logger.LogWarning("User {userId} password validation failed: {errors}.", await GetUserIdAsync(user),
string.Join(";", errors.Select(e => e.Code)));
return IdentityResult.Failed(errors.ToArray());
2022-08-29 16:06:55 -04:00
}
return IdentityResult.Success;
}
public void SetTwoFactorProvider(User user, TwoFactorProviderType type, bool setEnabled = true)
2022-08-29 16:06:55 -04:00
{
var providers = user.GetTwoFactorProviders();
if (!providers?.ContainsKey(type) ?? true)
2022-08-29 16:06:55 -04:00
{
return;
}
if (setEnabled)
{
providers[type].Enabled = true;
}
user.SetTwoFactorProviders(providers);
if (string.IsNullOrWhiteSpace(user.TwoFactorRecoveryCode))
{
user.TwoFactorRecoveryCode = CoreHelpers.SecureRandomString(32, upper: false, special: false);
}
2022-08-29 16:06:55 -04:00
}
private async Task CheckPoliciesOnTwoFactorRemovalAsync(User user, IOrganizationService organizationService)
{
[EC-787] Create a method in PolicyService to check if a policy applies to a user (#2537) * [EC-787] Add new stored procedure OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Add new method IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Add OrganizationUserPolicyDetails to represent policies applicable to a specific user * [EC-787] Add method IPolicyService.GetPoliciesApplicableToUser to filter the obtained policy data * [EC-787] Returning PolicyData on stored procedures * [EC-787] Changed GetPoliciesApplicableToUserAsync to return ICollection * [EC-787] Switched all usings of IPolicyRepository.GetManyByTypeApplicableToUserIdAsync to IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Removed policy logic from BaseRequestValidator and added usage of IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for IPolicyService.GetPoliciesApplicableToUserAsync * [EC-787] Added unit tests for OrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Changed integration test to check for single result * [EC-787] Marked IPolicyRepository methods GetManyByTypeApplicableToUserIdAsync and GetCountByTypeApplicableToUserIdAsync as obsolete * [EC-787] Returning OrganizationUserId on OrganizationUser_ReadByUserIdWithPolicyDetails * [EC-787] Remove deprecated stored procedures Policy_CountByTypeApplicableToUser, Policy_ReadByTypeApplicableToUser and function PolicyApplicableToUser * [EC-787] Added method IPolicyService.AnyPoliciesApplicableToUserAsync * [EC-787] Removed 'OrganizationUserType' parameter from queries * [EC-787] Formatted OrganizationUserPolicyDetailsCompare * [EC-787] Renamed SQL migration files * [EC-787] Changed OrganizationUser_ReadByUserIdWithPolicyDetails to return Permissions json * [EC-787] Refactored excluded user types for each Policy * [EC-787] Updated dates on dbo_future files * [EC-787] Remove dbo_future files from sql proj * [EC-787] Added parameter PolicyType to IOrganizationUserRepository.GetByUserIdWithPolicyDetailsAsync * [EC-787] Rewrote OrganizationUser_ReadByUserIdWithPolicyDetails and added parameter for PolicyType * Update util/Migrator/DbScripts/2023-03-10_00_OrganizationUserReadByUserIdWithPolicyDetails.sql Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
2023-05-12 08:22:19 +01:00
var twoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication);
var removeOrgUserTasks = twoFactorPolicies.Select(async p =>
{
await organizationService.DeleteUserAsync(p.OrganizationId, user.Id);
var organization = await _organizationRepository.GetByIdAsync(p.OrganizationId);
await _mailService.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(
organization.Name, user.Email);
}).ToArray();
await Task.WhenAll(removeOrgUserTasks);
}
2022-08-29 14:53:16 -04:00
public override async Task<IdentityResult> ConfirmEmailAsync(User user, string token)
2022-08-29 16:06:55 -04:00
{
var result = await base.ConfirmEmailAsync(user, token);
if (result.Succeeded)
{
await _referenceEventService.RaiseEventAsync(
new ReferenceEvent(ReferenceEventType.ConfirmEmailAddress, user, _currentContext));
}
return result;
2022-08-29 16:06:55 -04:00
}
public async Task RotateApiKeyAsync(User user)
2022-08-29 16:06:55 -04:00
{
2017-07-06 14:55:58 -04:00
user.ApiKey = CoreHelpers.SecureRandomString(30);
user.RevisionDate = DateTime.UtcNow;
await _userRepository.ReplaceAsync(user);
2022-08-29 16:06:55 -04:00
}
public async Task SendOTPAsync(User user)
2022-08-29 16:06:55 -04:00
{
if (user.Email == null)
{
throw new BadRequestException("No user email.");
}
var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultEmailProvider,
"otp:" + user.Email);
await _mailService.SendOTPEmailAsync(user.Email, token);
2022-08-29 16:06:55 -04:00
}
Trusted Device Encryption feature (#3151) * [PM-1203] feat: allow verification for all passwordless accounts (#3038) * [PM-1033] Org invite user creation flow 1 (#3028) * [PM-1033] feat: remove user verification from password enrollment * [PM-1033] feat: auto accept invitation when enrolling into password reset * [PM-1033] fix: controller tests * [PM-1033] refactor: `UpdateUserResetPasswordEnrollmentCommand` * [PM-1033] refactor(wip): make `AcceptUserCommand` * Revert "[PM-1033] refactor(wip): make `AcceptUserCommand`" This reverts commit dc1319e7fa70c4844bbc70e0b01089b682ac2843. * Revert "[PM-1033] refactor: `UpdateUserResetPasswordEnrollmentCommand`" This reverts commit 43df689c7f244af4f7ffec1f9768a72081a624c3. * [PM-1033] refactor: move invite accept to controller This avoids creating yet another method that depends on having `IUserService` passed in as a parameter * [PM-1033] fix: add missing changes * [PM-1381] Add Trusted Device Keys to Auth Response (#3066) * Return Keys for Trusted Device - Check whether the current logging in device is trusted - Return their keys on successful login * Formatting * Address PR Feedback * Add Remarks Comment * [PM-1338] `AuthRequest` Event Logs (#3046) * Update AuthRequestController - Only allow AdminApproval Requests to be created from authed endpoint - Add endpoint that has authentication to be able to create admin approval * Add PasswordlessAuthSettings - Add settings for customizing expiration times * Add new EventTypes * Add Logic for AdminApproval Type - Add logic for validating AdminApproval expiration - Add event logging for Approval/Disapproval of AdminApproval - Add logic for creating AdminApproval types * Add Test Helpers - Change BitAutoData to allow you to use string representations of common types. * Add/Update AuthRequestService Tests * Run Formatting * Switch to 7 Days * Add Test Covering ResponseDate Being Set * Address PR Feedback - Create helper for checking if date is expired - Move validation logic into smaller methods * Switch to User Event Type - Make RequestDeviceApproval user type - User types will log for each org user is in * [PM-2998] Move Approving Device Check (#3101) * Move Check for Approving Devices - Exclude currently logging in device - Remove old way of checking - Add tests asserting behavior * Update DeviceType list * Update Naming & Address PR Feedback * Fix Tests * Address PR Feedback * Formatting * Now Fully Update Naming? * Feature/auth/pm 2759/add can reset password to user decryption options (#3113) * PM-2759 - BaseRequestValidator.cs - CreateUserDecryptionOptionsAsync - Add new hasManageResetPasswordPermission for post SSO redirect logic required on client. * PM-2759 - Update IdentityServerSsoTests.cs to all pass based on the addition of HasManageResetPasswordPermission to TrustedDeviceUserDecryptionOption * IdentityServerSsoTests.cs - fix typo in test name: LoggingApproval --> LoginApproval * PM1259 - Add test case for verifying that TrustedDeviceOption.hasManageResetPasswordPermission is set properly based on user permission * dotnet format run * Feature/auth/pm 2759/add can reset password to user decryption options fix jit users (#3120) * PM-2759 - IdentityServer - CreateUserDecryptionOptionsAsync - hasManageResetPasswordPermission set logic was broken for JIT provisioned users as I assumed we would always have a list of at least 1 org during the SSO process. Added TODO for future test addition but getting this out there now as QA is blocked by being unable to create JIT provisioned users. * dotnet format * Tiny tweak * [PM-1339] Allow Rotating Device Keys (#3096) * Allow Rotation of Trusted Device Keys - Add endpoint for getting keys relating to rotation - Add endpoint for rotating your current device - In the same endpoint allow a list of other devices to rotate * Formatting * Use Extension Method * Add Tests from PR Co-authored-by: Jared Snider <jsnider@bitwarden.com> --------- Co-authored-by: Jared Snider <jsnider@bitwarden.com> * Check the user directly if they have the ResetPasswordKey (#3153) * PM-3327 - UpdateKeyAsync must exempt the currently calling device from the logout notification in order to prevent prematurely logging the user out before the client side key rotation process can complete. The calling device will log itself out once it is done. (#3170) * Allow OTP Requests When Users Are On TDE (#3184) * [PM-3356][PM-3292] Allow OTP For All (#3188) * Allow OTP For All - On a trusted device isn't a good check because a user might be using a trusted device locally but not trusted it long term - The logic wasn't working for KC users anyways * Remove Old Comment * [AC-1601] Added RequireSso policy as a dependency of TDE (#3209) * Added RequireSso policy as a dependency of TDE. * Added test for RequireSso for TDE. * Added save. * Fixed policy name. --------- Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com> Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Co-authored-by: Jared Snider <jsnider@bitwarden.com>
2023-08-17 16:03:06 -04:00
public async Task<bool> VerifyOTPAsync(User user, string token)
2022-08-29 16:06:55 -04:00
{
Trusted Device Encryption feature (#3151) * [PM-1203] feat: allow verification for all passwordless accounts (#3038) * [PM-1033] Org invite user creation flow 1 (#3028) * [PM-1033] feat: remove user verification from password enrollment * [PM-1033] feat: auto accept invitation when enrolling into password reset * [PM-1033] fix: controller tests * [PM-1033] refactor: `UpdateUserResetPasswordEnrollmentCommand` * [PM-1033] refactor(wip): make `AcceptUserCommand` * Revert "[PM-1033] refactor(wip): make `AcceptUserCommand`" This reverts commit dc1319e7fa70c4844bbc70e0b01089b682ac2843. * Revert "[PM-1033] refactor: `UpdateUserResetPasswordEnrollmentCommand`" This reverts commit 43df689c7f244af4f7ffec1f9768a72081a624c3. * [PM-1033] refactor: move invite accept to controller This avoids creating yet another method that depends on having `IUserService` passed in as a parameter * [PM-1033] fix: add missing changes * [PM-1381] Add Trusted Device Keys to Auth Response (#3066) * Return Keys for Trusted Device - Check whether the current logging in device is trusted - Return their keys on successful login * Formatting * Address PR Feedback * Add Remarks Comment * [PM-1338] `AuthRequest` Event Logs (#3046) * Update AuthRequestController - Only allow AdminApproval Requests to be created from authed endpoint - Add endpoint that has authentication to be able to create admin approval * Add PasswordlessAuthSettings - Add settings for customizing expiration times * Add new EventTypes * Add Logic for AdminApproval Type - Add logic for validating AdminApproval expiration - Add event logging for Approval/Disapproval of AdminApproval - Add logic for creating AdminApproval types * Add Test Helpers - Change BitAutoData to allow you to use string representations of common types. * Add/Update AuthRequestService Tests * Run Formatting * Switch to 7 Days * Add Test Covering ResponseDate Being Set * Address PR Feedback - Create helper for checking if date is expired - Move validation logic into smaller methods * Switch to User Event Type - Make RequestDeviceApproval user type - User types will log for each org user is in * [PM-2998] Move Approving Device Check (#3101) * Move Check for Approving Devices - Exclude currently logging in device - Remove old way of checking - Add tests asserting behavior * Update DeviceType list * Update Naming & Address PR Feedback * Fix Tests * Address PR Feedback * Formatting * Now Fully Update Naming? * Feature/auth/pm 2759/add can reset password to user decryption options (#3113) * PM-2759 - BaseRequestValidator.cs - CreateUserDecryptionOptionsAsync - Add new hasManageResetPasswordPermission for post SSO redirect logic required on client. * PM-2759 - Update IdentityServerSsoTests.cs to all pass based on the addition of HasManageResetPasswordPermission to TrustedDeviceUserDecryptionOption * IdentityServerSsoTests.cs - fix typo in test name: LoggingApproval --> LoginApproval * PM1259 - Add test case for verifying that TrustedDeviceOption.hasManageResetPasswordPermission is set properly based on user permission * dotnet format run * Feature/auth/pm 2759/add can reset password to user decryption options fix jit users (#3120) * PM-2759 - IdentityServer - CreateUserDecryptionOptionsAsync - hasManageResetPasswordPermission set logic was broken for JIT provisioned users as I assumed we would always have a list of at least 1 org during the SSO process. Added TODO for future test addition but getting this out there now as QA is blocked by being unable to create JIT provisioned users. * dotnet format * Tiny tweak * [PM-1339] Allow Rotating Device Keys (#3096) * Allow Rotation of Trusted Device Keys - Add endpoint for getting keys relating to rotation - Add endpoint for rotating your current device - In the same endpoint allow a list of other devices to rotate * Formatting * Use Extension Method * Add Tests from PR Co-authored-by: Jared Snider <jsnider@bitwarden.com> --------- Co-authored-by: Jared Snider <jsnider@bitwarden.com> * Check the user directly if they have the ResetPasswordKey (#3153) * PM-3327 - UpdateKeyAsync must exempt the currently calling device from the logout notification in order to prevent prematurely logging the user out before the client side key rotation process can complete. The calling device will log itself out once it is done. (#3170) * Allow OTP Requests When Users Are On TDE (#3184) * [PM-3356][PM-3292] Allow OTP For All (#3188) * Allow OTP For All - On a trusted device isn't a good check because a user might be using a trusted device locally but not trusted it long term - The logic wasn't working for KC users anyways * Remove Old Comment * [AC-1601] Added RequireSso policy as a dependency of TDE (#3209) * Added RequireSso policy as a dependency of TDE. * Added test for RequireSso for TDE. * Added save. * Fixed policy name. --------- Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com> Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Co-authored-by: Jared Snider <jsnider@bitwarden.com>
2023-08-17 16:03:06 -04:00
return await base.VerifyUserTokenAsync(user, TokenOptions.DefaultEmailProvider,
"otp:" + user.Email, token);
2022-08-29 16:06:55 -04:00
}
public async Task<bool> VerifySecretAsync(User user, string secret)
2022-08-29 16:06:55 -04:00
{
Trusted Device Encryption feature (#3151) * [PM-1203] feat: allow verification for all passwordless accounts (#3038) * [PM-1033] Org invite user creation flow 1 (#3028) * [PM-1033] feat: remove user verification from password enrollment * [PM-1033] feat: auto accept invitation when enrolling into password reset * [PM-1033] fix: controller tests * [PM-1033] refactor: `UpdateUserResetPasswordEnrollmentCommand` * [PM-1033] refactor(wip): make `AcceptUserCommand` * Revert "[PM-1033] refactor(wip): make `AcceptUserCommand`" This reverts commit dc1319e7fa70c4844bbc70e0b01089b682ac2843. * Revert "[PM-1033] refactor: `UpdateUserResetPasswordEnrollmentCommand`" This reverts commit 43df689c7f244af4f7ffec1f9768a72081a624c3. * [PM-1033] refactor: move invite accept to controller This avoids creating yet another method that depends on having `IUserService` passed in as a parameter * [PM-1033] fix: add missing changes * [PM-1381] Add Trusted Device Keys to Auth Response (#3066) * Return Keys for Trusted Device - Check whether the current logging in device is trusted - Return their keys on successful login * Formatting * Address PR Feedback * Add Remarks Comment * [PM-1338] `AuthRequest` Event Logs (#3046) * Update AuthRequestController - Only allow AdminApproval Requests to be created from authed endpoint - Add endpoint that has authentication to be able to create admin approval * Add PasswordlessAuthSettings - Add settings for customizing expiration times * Add new EventTypes * Add Logic for AdminApproval Type - Add logic for validating AdminApproval expiration - Add event logging for Approval/Disapproval of AdminApproval - Add logic for creating AdminApproval types * Add Test Helpers - Change BitAutoData to allow you to use string representations of common types. * Add/Update AuthRequestService Tests * Run Formatting * Switch to 7 Days * Add Test Covering ResponseDate Being Set * Address PR Feedback - Create helper for checking if date is expired - Move validation logic into smaller methods * Switch to User Event Type - Make RequestDeviceApproval user type - User types will log for each org user is in * [PM-2998] Move Approving Device Check (#3101) * Move Check for Approving Devices - Exclude currently logging in device - Remove old way of checking - Add tests asserting behavior * Update DeviceType list * Update Naming & Address PR Feedback * Fix Tests * Address PR Feedback * Formatting * Now Fully Update Naming? * Feature/auth/pm 2759/add can reset password to user decryption options (#3113) * PM-2759 - BaseRequestValidator.cs - CreateUserDecryptionOptionsAsync - Add new hasManageResetPasswordPermission for post SSO redirect logic required on client. * PM-2759 - Update IdentityServerSsoTests.cs to all pass based on the addition of HasManageResetPasswordPermission to TrustedDeviceUserDecryptionOption * IdentityServerSsoTests.cs - fix typo in test name: LoggingApproval --> LoginApproval * PM1259 - Add test case for verifying that TrustedDeviceOption.hasManageResetPasswordPermission is set properly based on user permission * dotnet format run * Feature/auth/pm 2759/add can reset password to user decryption options fix jit users (#3120) * PM-2759 - IdentityServer - CreateUserDecryptionOptionsAsync - hasManageResetPasswordPermission set logic was broken for JIT provisioned users as I assumed we would always have a list of at least 1 org during the SSO process. Added TODO for future test addition but getting this out there now as QA is blocked by being unable to create JIT provisioned users. * dotnet format * Tiny tweak * [PM-1339] Allow Rotating Device Keys (#3096) * Allow Rotation of Trusted Device Keys - Add endpoint for getting keys relating to rotation - Add endpoint for rotating your current device - In the same endpoint allow a list of other devices to rotate * Formatting * Use Extension Method * Add Tests from PR Co-authored-by: Jared Snider <jsnider@bitwarden.com> --------- Co-authored-by: Jared Snider <jsnider@bitwarden.com> * Check the user directly if they have the ResetPasswordKey (#3153) * PM-3327 - UpdateKeyAsync must exempt the currently calling device from the logout notification in order to prevent prematurely logging the user out before the client side key rotation process can complete. The calling device will log itself out once it is done. (#3170) * Allow OTP Requests When Users Are On TDE (#3184) * [PM-3356][PM-3292] Allow OTP For All (#3188) * Allow OTP For All - On a trusted device isn't a good check because a user might be using a trusted device locally but not trusted it long term - The logic wasn't working for KC users anyways * Remove Old Comment * [AC-1601] Added RequireSso policy as a dependency of TDE (#3209) * Added RequireSso policy as a dependency of TDE. * Added test for RequireSso for TDE. * Added save. * Fixed policy name. --------- Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> Co-authored-by: Justin Baur <19896123+justindbaur@users.noreply.github.com> Co-authored-by: Vincent Salucci <vincesalucci21@gmail.com> Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Co-authored-by: Jared Snider <jsnider@bitwarden.com>
2023-08-17 16:03:06 -04:00
bool isVerified;
if (user.HasMasterPassword())
{
// If the user has a master password the secret is most likely going to be a hash
// of their password, but in certain scenarios, like when the user has logged into their
// device without a password (trusted device encryption) but the account
// does still have a password we will allow the use of OTP.
isVerified = await CheckPasswordAsync(user, secret) ||
await VerifyOTPAsync(user, secret);
}
else
{
// If they don't have a password at all they can only do OTP
isVerified = await VerifyOTPAsync(user, secret);
}
return isVerified;
2022-08-29 16:06:55 -04:00
}
2015-12-08 22:57:38 -05:00
}