2015-12-08 22:57:38 -05:00
using System ;
using System.Collections.Generic ;
using System.Threading.Tasks ;
2016-05-19 19:10:24 -04:00
using Microsoft.AspNetCore.Identity ;
2015-12-08 22:57:38 -05:00
using Microsoft.Extensions.Logging ;
2016-05-19 19:10:24 -04:00
using Microsoft.Extensions.Options ;
2017-03-08 21:45:08 -05:00
using Bit.Core.Models.Table ;
2015-12-08 22:57:38 -05:00
using Bit.Core.Repositories ;
using System.Linq ;
2016-11-14 23:32:15 -05:00
using Bit.Core.Enums ;
2017-01-14 10:02:37 -05:00
using System.Security.Claims ;
2017-06-07 14:14:34 -04:00
using Bit.Core.Models ;
2017-06-21 22:33:45 -04:00
using Bit.Core.Models.Business ;
2017-06-24 09:20:12 -04:00
using U2fLib = U2F . Core . Crypto . U2F ;
using U2F.Core.Models ;
using U2F.Core.Utils ;
2021-02-04 12:54:21 -06:00
using Bit.Core.Context ;
2017-07-01 23:20:19 -04:00
using Bit.Core.Exceptions ;
2017-07-06 14:55:58 -04:00
using Bit.Core.Utilities ;
2021-02-22 15:35:16 -06:00
using Bit.Core.Settings ;
2017-08-11 17:06:31 -04:00
using System.IO ;
using Newtonsoft.Json ;
2018-05-24 16:53:07 -04:00
using Microsoft.AspNetCore.DataProtection ;
2021-03-22 23:21:43 +01:00
using Fido2NetLib ;
using Fido2NetLib.Objects ;
2015-12-08 22:57:38 -05:00
namespace Bit.Core.Services
{
public class UserService : UserManager < User > , IUserService , IDisposable
{
2017-07-06 14:55:58 -04:00
private const string PremiumPlanId = "premium-annually" ;
private const string StoragePlanId = "storage-gb-annually" ;
2015-12-08 22:57:38 -05:00
private readonly IUserRepository _userRepository ;
private readonly ICipherRepository _cipherRepository ;
2017-04-27 17:28:39 -04:00
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 ;
2018-08-28 16:23:58 -04:00
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 ;
2018-05-24 16:53:07 -04:00
private readonly IDataProtector _organizationServiceDataProtector ;
2020-07-07 12:01:34 -04:00
private readonly IReferenceEventService _referenceEventService ;
2021-03-22 23:21:43 +01:00
private readonly IFido2 _fido2 ;
2021-02-04 12:54:21 -06:00
private readonly ICurrentContext _currentContext ;
2017-06-21 22:33:45 -04:00
private readonly GlobalSettings _globalSettings ;
2020-10-13 15:00:33 -05:00
private readonly IOrganizationService _organizationService ;
2021-09-15 20:34:06 +02:00
private readonly IProviderUserRepository _providerUserRepository ;
2015-12-08 22:57:38 -05:00
public UserService (
IUserRepository userRepository ,
ICipherRepository cipherRepository ,
2017-04-27 17:28:39 -04:00
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 ,
2017-01-24 22:15:21 -05:00
ILogger < UserManager < User > > logger ,
2017-08-11 22:55:25 -04:00
ILicensingService licenseService ,
2017-12-01 10:07:14 -05:00
IEventService eventService ,
2018-08-28 16:23:58 -04:00
IApplicationCacheService applicationCacheService ,
2018-05-24 16:53:07 -04:00
IDataProtectionProvider dataProtectionProvider ,
2019-02-08 23:53:09 -05:00
IPaymentService paymentService ,
2020-02-19 14:56:16 -05:00
IPolicyRepository policyRepository ,
2020-07-07 12:01:34 -04:00
IReferenceEventService referenceEventService ,
2021-03-22 23:21:43 +01:00
IFido2 fido2 ,
2021-02-04 12:54:21 -06:00
ICurrentContext currentContext ,
2020-10-13 15:00:33 -05:00
GlobalSettings globalSettings ,
2021-07-02 06:27:03 +10:00
IOrganizationService organizationService ,
2021-09-15 20:34:06 +02:00
IProviderUserRepository providerUserRepository )
2015-12-08 22:57:38 -05:00
: base (
store ,
optionsAccessor ,
passwordHasher ,
userValidators ,
passwordValidators ,
keyNormalizer ,
errors ,
services ,
2016-05-19 19:10:24 -04:00
logger )
2015-12-08 22:57:38 -05:00
{
_userRepository = userRepository ;
_cipherRepository = cipherRepository ;
2017-04-27 17:28:39 -04:00
_organizationUserRepository = organizationUserRepository ;
2018-12-03 10:56:55 -05:00
_organizationRepository = organizationRepository ;
2015-12-08 22:57:38 -05:00
_mailService = mailService ;
2017-04-21 14:22:32 -04:00
_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 ;
2018-08-28 16:23:58 -04:00
_applicationCacheService = applicationCacheService ;
2019-02-08 23:53:09 -05:00
_paymentService = paymentService ;
2020-02-19 14:56:16 -05:00
_policyRepository = policyRepository ;
2018-05-24 16:53:07 -04:00
_organizationServiceDataProtector = dataProtectionProvider . CreateProtector (
"OrganizationServiceDataProtector" ) ;
2020-07-07 12:01:34 -04:00
_referenceEventService = referenceEventService ;
2021-03-22 23:21:43 +01:00
_fido2 = fido2 ;
2017-01-24 22:15:21 -05:00
_currentContext = currentContext ;
2017-06-21 22:33:45 -04:00
_globalSettings = globalSettings ;
2020-10-13 15:00:33 -05:00
_organizationService = organizationService ;
2021-09-15 20:34:06 +02:00
_providerUserRepository = providerUserRepository ;
2015-12-08 22:57:38 -05:00
}
2017-01-14 10:02:37 -05:00
public Guid ? GetProperUserId ( ClaimsPrincipal principal )
{
2020-03-27 14:36:37 -04:00
if ( ! Guid . TryParse ( GetUserId ( principal ) , out var userIdGuid ) )
2017-01-14 10:02:37 -05:00
{
return null ;
}
return userIdGuid ;
}
2017-01-11 21:46:36 -05:00
public async Task < User > GetUserByIdAsync ( string userId )
{
2020-03-27 14:36:37 -04:00
if ( _currentContext ? . User ! = null & &
2017-01-24 22:15:21 -05:00
string . Equals ( _currentContext . User . Id . ToString ( ) , userId , StringComparison . InvariantCultureIgnoreCase ) )
{
return _currentContext . User ;
}
2020-03-27 14:36:37 -04:00
if ( ! Guid . TryParse ( userId , out var userIdGuid ) )
2017-01-11 21:46:36 -05:00
{
return null ;
}
2017-01-24 22:15:21 -05:00
_currentContext . User = await _userRepository . GetByIdAsync ( userIdGuid ) ;
return _currentContext . User ;
2017-01-11 21:46:36 -05:00
}
2016-05-21 17:16:22 -04:00
public async Task < User > GetUserByIdAsync ( Guid userId )
2015-12-08 22:57:38 -05:00
{
2020-03-27 14:36:37 -04:00
if ( _currentContext ? . User ! = null & & _currentContext . User . Id = = userId )
2017-01-24 22:15:21 -05:00
{
return _currentContext . User ;
}
_currentContext . User = await _userRepository . GetByIdAsync ( userId ) ;
return _currentContext . User ;
2015-12-08 22:57:38 -05:00
}
2017-01-24 22:46:54 -05:00
public async Task < User > GetUserByPrincipalAsync ( ClaimsPrincipal principal )
{
var userId = GetProperUserId ( principal ) ;
2020-03-27 14:36:37 -04:00
if ( ! userId . HasValue )
2017-01-24 22:46:54 -05:00
{
return null ;
}
return await GetUserByIdAsync ( userId . Value ) ;
}
2017-01-14 10:02:37 -05:00
public async Task < DateTime > GetAccountRevisionDateByIdAsync ( Guid userId )
{
return await _userRepository . GetAccountRevisionDateAsync ( userId ) ;
}
2017-08-14 13:06:44 -04:00
public async Task SaveUserAsync ( User user , bool push = false )
2015-12-08 22:57:38 -05:00
{
2020-03-27 14:36:37 -04:00
if ( user . Id = = default ( Guid ) )
2015-12-08 22:57:38 -05:00
{
throw new ApplicationException ( "Use register method to create a new user." ) ;
}
2016-02-21 00:15:17 -05:00
2017-01-14 10:02:37 -05:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
2015-12-08 22:57:38 -05:00
await _userRepository . ReplaceAsync ( user ) ;
2017-04-21 14:22:32 -04:00
2020-03-27 14:36:37 -04:00
if ( push )
2017-08-14 13:06:44 -04:00
{
// push
await _pushService . PushSyncSettingsAsync ( user . Id ) ;
}
2015-12-08 22:57:38 -05:00
}
2017-04-27 17:28:39 -04:00
public override async Task < IdentityResult > DeleteAsync ( User user )
{
2017-10-25 11:36:54 -04:00
// Check if user is the only owner of any organizations.
var onlyOwnerCount = await _organizationUserRepository . GetCountByOnlyOwnerAsync ( user . Id ) ;
2020-03-27 14:36:37 -04:00
if ( onlyOwnerCount > 0 )
2017-04-27 17:28:39 -04:00
{
2018-12-03 10:56:55 -05:00
var deletedOrg = false ;
var orgs = await _organizationUserRepository . GetManyDetailsByUserAsync ( user . Id ,
OrganizationUserStatusType . Confirmed ) ;
2020-03-27 14:36:37 -04:00
if ( orgs . Count = = 1 )
2017-04-27 17:28:39 -04:00
{
2018-12-03 10:56:55 -05:00
var org = await _organizationRepository . GetByIdAsync ( orgs . First ( ) . OrganizationId ) ;
2020-03-27 14:36:37 -04:00
if ( org ! = null & & ( ! org . Enabled | | string . IsNullOrWhiteSpace ( org . GatewaySubscriptionId ) ) )
2018-12-03 10:56:55 -05:00
{
var orgCount = await _organizationUserRepository . GetCountByOrganizationIdAsync ( org . Id ) ;
2020-03-27 14:36:37 -04:00
if ( orgCount < = 1 )
2018-12-03 10:56:55 -05:00
{
await _organizationRepository . DeleteAsync ( org ) ;
deletedOrg = true ;
}
}
}
2020-03-27 14:36:37 -04:00
if ( ! deletedOrg )
2018-12-03 10:56:55 -05:00
{
return IdentityResult . Failed ( new IdentityError
{
2021-09-15 20:34:06 +02:00
Description = "Cannot delete this user because it is the sole owner of at least one organization. Please delete these organizations or upgrade another user." ,
2018-12-03 10:56:55 -05:00
} ) ;
}
2017-04-27 17:28:39 -04:00
}
2021-09-15 20:34:06 +02:00
var onlyOwnerProviderCount = await _providerUserRepository . GetCountByOnlyOwnerAsync ( user . Id ) ;
if ( onlyOwnerProviderCount > 0 )
{
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." ,
} ) ;
}
2020-03-27 14:36:37 -04:00
if ( ! string . IsNullOrWhiteSpace ( user . GatewaySubscriptionId ) )
2017-07-11 11:19:58 -04:00
{
2018-09-07 14:00:56 -04:00
try
{
2019-09-20 13:45:47 -04:00
await CancelPremiumAsync ( user , null , true ) ;
2018-09-07 14:00:56 -04:00
}
2020-03-27 14:36:37 -04:00
catch ( GatewayException ) { }
2017-07-11 11:19:58 -04:00
}
2017-04-27 17:28:39 -04:00
await _userRepository . DeleteAsync ( user ) ;
2020-07-07 12:01:34 -04:00
await _referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . DeleteAccount , user ) ) ;
2018-08-28 08:29:54 -04:00
await _pushService . PushLogOutAsync ( user . Id ) ;
2017-04-27 17:28:39 -04:00
return IdentityResult . Success ;
}
2017-08-09 10:53:42 -04:00
public async Task < IdentityResult > DeleteAsync ( User user , string token )
{
2020-03-27 14:36:37 -04:00
if ( ! ( await VerifyUserTokenAsync ( user , TokenOptions . DefaultProvider , "DeleteAccount" , token ) ) )
2017-08-09 10:53:42 -04:00
{
return IdentityResult . Failed ( ErrorDescriber . InvalidToken ( ) ) ;
}
return await DeleteAsync ( user ) ;
}
public async Task SendDeleteConfirmationAsync ( string email )
{
var user = await _userRepository . GetByEmailAsync ( email ) ;
2020-03-27 14:36:37 -04:00
if ( user = = null )
2017-08-09 10:53:42 -04:00
{
// No user exists.
return ;
}
var token = await base . GenerateUserTokenAsync ( user , TokenOptions . DefaultProvider , "DeleteAccount" ) ;
await _mailService . SendVerifyDeleteEmailAsync ( user . Email , user . Id , token ) ;
}
2018-05-24 16:53:07 -04:00
public async Task < IdentityResult > RegisterUserAsync ( User user , string masterPassword ,
string token , Guid ? orgUserId )
2015-12-08 22:57:38 -05:00
{
2018-05-24 16:53:07 -04:00
var tokenValid = false ;
2020-03-27 14:36:37 -04:00
if ( _globalSettings . DisableUserRegistration & & ! string . IsNullOrWhiteSpace ( token ) & & orgUserId . HasValue )
2017-08-28 07:50:57 -04:00
{
2018-07-31 08:19:49 -04:00
tokenValid = CoreHelpers . UserInviteTokenIsValid ( _organizationServiceDataProtector , token ,
2019-06-11 17:17:23 -04:00
user . Email , orgUserId . Value , _globalSettings ) ;
2018-05-24 16:53:07 -04:00
}
2020-03-27 14:36:37 -04:00
if ( _globalSettings . DisableUserRegistration & & ! tokenValid )
2018-05-24 16:53:07 -04:00
{
throw new BadRequestException ( "Open registration has been disabled by the system administrator." ) ;
2017-08-28 07:50:57 -04:00
}
2020-03-27 14:36:37 -04:00
if ( orgUserId . HasValue )
2020-02-28 09:14:33 -05:00
{
var orgUser = await _organizationUserRepository . GetByIdAsync ( orgUserId . Value ) ;
2020-03-27 14:36:37 -04:00
if ( orgUser ! = null )
2020-02-28 09:14:33 -05:00
{
var twoFactorPolicy = await _policyRepository . GetByOrganizationIdTypeAsync ( orgUser . OrganizationId ,
PolicyType . TwoFactorAuthentication ) ;
2020-03-27 14:36:37 -04:00
if ( twoFactorPolicy ! = null & & twoFactorPolicy . Enabled )
2020-02-28 09:14:33 -05:00
{
user . SetTwoFactorProviders ( new Dictionary < TwoFactorProviderType , TwoFactorProvider >
{
[TwoFactorProviderType.Email] = new TwoFactorProvider
{
MetaData = new Dictionary < string , object > { [ "Email" ] = user . Email . ToLowerInvariant ( ) } ,
Enabled = true
}
} ) ;
SetTwoFactorProvider ( user , TwoFactorProviderType . Email ) ;
}
}
}
2020-11-10 15:15:29 -05:00
user . ApiKey = CoreHelpers . SecureRandomString ( 30 ) ;
2015-12-08 22:57:38 -05:00
var result = await base . CreateAsync ( user , masterPassword ) ;
2020-03-27 14:36:37 -04:00
if ( result = = IdentityResult . Success )
2015-12-08 22:57:38 -05:00
{
await _mailService . SendWelcomeEmailAsync ( user ) ;
2020-08-13 17:30:10 -04:00
await _referenceEventService . RaiseEventAsync ( new ReferenceEvent ( ReferenceEventType . Signup , user ) ) ;
}
return result ;
}
public async Task < IdentityResult > RegisterUserAsync ( User user )
{
var result = await base . CreateAsync ( user ) ;
if ( result = = IdentityResult . Success )
{
await _mailService . SendWelcomeEmailAsync ( user ) ;
await _referenceEventService . RaiseEventAsync ( new ReferenceEvent ( ReferenceEventType . Signup , user ) ) ;
2015-12-08 22:57:38 -05:00
}
return result ;
}
public async Task SendMasterPasswordHintAsync ( string email )
{
var user = await _userRepository . GetByEmailAsync ( email ) ;
2020-03-27 14:36:37 -04:00
if ( user = = null )
2015-12-08 22:57:38 -05:00
{
// No user exists. Do we want to send an email telling them this in the future?
return ;
}
2020-03-27 14:36:37 -04:00
if ( string . IsNullOrWhiteSpace ( user . MasterPasswordHint ) )
2015-12-08 22:57:38 -05:00
{
await _mailService . SendNoMasterPasswordHintEmailAsync ( email ) ;
2016-07-23 00:30:58 -04:00
return ;
2015-12-08 22:57:38 -05:00
}
await _mailService . SendMasterPasswordHintEmailAsync ( email , user . MasterPasswordHint ) ;
}
2017-06-20 10:08:59 -04:00
public async Task SendTwoFactorEmailAsync ( User user )
2017-06-20 09:21:35 -04:00
{
2017-06-20 10:08:59 -04:00
var provider = user . GetTwoFactorProvider ( TwoFactorProviderType . Email ) ;
2020-03-27 14:36:37 -04:00
if ( provider = = null | | provider . MetaData = = null | | ! provider . MetaData . ContainsKey ( "Email" ) )
2017-06-20 09:21:35 -04:00
{
2017-06-20 10:08:59 -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 ( ) ;
2017-06-20 10:08:59 -04:00
var token = await base . GenerateUserTokenAsync ( user , TokenOptions . DefaultEmailProvider ,
2017-11-02 23:29:58 -04:00
"2faEmail:" + email ) ;
await _mailService . SendTwoFactorEmailAsync ( email , token ) ;
2017-06-20 09:21:35 -04:00
}
2017-06-20 10:08:59 -04:00
public async Task < bool > VerifyTwoFactorEmailAsync ( User user , string token )
2017-06-20 09:21:35 -04:00
{
2017-06-20 10:08:59 -04:00
var provider = user . GetTwoFactorProvider ( TwoFactorProviderType . Email ) ;
2020-03-27 14:36:37 -04:00
if ( provider = = null | | provider . MetaData = = null | | ! provider . MetaData . ContainsKey ( "Email" ) )
2017-06-20 09:21:35 -04:00
{
2017-06-20 10:08:59 -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 ( ) ;
2017-06-20 10:08:59 -04:00
return await base . VerifyUserTokenAsync ( user , TokenOptions . DefaultEmailProvider ,
2017-11-02 23:29:58 -04:00
"2faEmail:" + email , token ) ;
2017-06-20 09:21:35 -04:00
}
2021-03-22 23:21:43 +01:00
public async Task < CredentialCreateOptions > StartWebAuthnRegistrationAsync ( User user )
2017-06-21 22:33:45 -04:00
{
2021-03-22 23:21:43 +01:00
var providers = user . GetTwoFactorProviders ( ) ;
if ( providers = = null )
2017-06-21 22:33:45 -04:00
{
2021-03-22 23:21:43 +01:00
providers = new Dictionary < TwoFactorProviderType , TwoFactorProvider > ( ) ;
2017-06-21 22:33:45 -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
{
Enabled = false
} ;
2017-06-21 22:33:45 -04:00
}
2021-03-22 23:21:43 +01:00
if ( provider . MetaData = = null )
2018-10-10 15:21:54 -04:00
{
2021-03-22 23:21:43 +01:00
provider . MetaData = new Dictionary < string , object > ( ) ;
}
2017-06-21 22:33:45 -04:00
2021-03-22 23:21:43 +01:00
var fidoUser = new Fido2User
{
DisplayName = user . Name ,
Name = user . Email ,
Id = user . Id . ToByteArray ( ) ,
} ;
2017-06-21 22:33:45 -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 ( ) ;
2017-06-21 22:33:45 -04:00
2021-05-12 18:46:35 +02:00
var authenticatorSelection = new AuthenticatorSelection
{
AuthenticatorAttachment = null ,
RequireResidentKey = false ,
UserVerification = UserVerificationRequirement . Discouraged
} ;
var options = _fido2 . RequestNewCredential ( fidoUser , excludeCredentials , authenticatorSelection , AttestationConveyancePreference . None ) ;
2018-10-08 14:38:11 -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 ) ;
2018-10-08 14:38:11 -04:00
2021-03-22 23:21:43 +01:00
return options ;
}
2018-10-08 14:38:11 -04:00
2021-03-22 23:21:43 +01:00
public async Task < bool > CompleteWebAuthRegistrationAsync ( User user , int id , string name , AuthenticatorAttestationRawResponse attestationResponse )
{
var keyId = $"Key{id}" ;
2018-10-10 15:21:54 -04:00
2021-03-22 23:21:43 +01:00
var provider = user . GetTwoFactorProvider ( TwoFactorProviderType . WebAuthn ) ;
if ( ! provider ? . MetaData ? . ContainsKey ( "pending" ) ? ? true )
2018-10-08 14:38:11 -04:00
{
2018-10-10 15:21:54 -04:00
return false ;
2018-10-08 14:38:11 -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 = > Task . FromResult ( true ) ;
var success = await _fido2 . MakeNewCredentialAsync ( attestationResponse , options , callback ) ;
provider . MetaData . Remove ( "pending" ) ;
provider . MetaData [ keyId ] = new TwoFactorProvider . WebAuthnData
{
Name = name ,
Descriptor = new PublicKeyCredentialDescriptor ( success . Result . CredentialId ) ,
PublicKey = success . Result . PublicKey ,
UserHandle = success . Result . User . Id ,
SignatureCounter = success . Result . Counter ,
CredType = success . Result . CredType ,
RegDate = DateTime . Now ,
AaGuid = success . Result . Aaguid
} ;
var providers = user . GetTwoFactorProviders ( ) ;
providers [ TwoFactorProviderType . WebAuthn ] = provider ;
user . SetTwoFactorProviders ( providers ) ;
await UpdateTwoFactorProviderAsync ( user , TwoFactorProviderType . WebAuthn ) ;
return true ;
2018-10-08 14:38:11 -04:00
}
2017-06-21 22:33:45 -04:00
2021-03-22 23:21:43 +01:00
public async Task < bool > DeleteWebAuthnKeyAsync ( User user , int id )
2018-10-08 14:38:11 -04:00
{
var providers = user . GetTwoFactorProviders ( ) ;
2020-03-27 14:36:37 -04:00
if ( providers = = null )
2018-10-08 14:38:11 -04:00
{
return false ;
}
var keyName = $"Key{id}" ;
2021-03-22 23:21:43 +01:00
var provider = user . GetTwoFactorProvider ( TwoFactorProviderType . WebAuthn ) ;
2020-03-27 14:36:37 -04:00
if ( ! provider ? . MetaData ? . ContainsKey ( keyName ) ? ? true )
2018-10-08 14:38:11 -04:00
{
return false ;
}
2020-03-27 14:36:37 -04:00
if ( provider . MetaData . Count < 2 )
2018-10-08 14:38:11 -04:00
{
return false ;
}
2021-05-05 16:14:49 +02:00
// Delete U2F token is this is a migrated WebAuthn token.
var entry = new TwoFactorProvider . WebAuthnData ( provider . MetaData [ keyName ] ) ;
if ( entry ? . Migrated ? ? false )
{
var u2fProvider = user . GetTwoFactorProvider ( TwoFactorProviderType . U2f ) ;
if ( u2fProvider ? . MetaData ? . ContainsKey ( keyName ) ? ? false )
{
u2fProvider . MetaData . Remove ( keyName ) ;
if ( u2fProvider . MetaData . Count > 0 )
{
providers [ TwoFactorProviderType . U2f ] = u2fProvider ;
}
else
{
providers . Remove ( TwoFactorProviderType . U2f ) ;
}
}
}
2018-10-08 14:38:11 -04:00
provider . MetaData . Remove ( keyName ) ;
2021-03-22 23:21:43 +01:00
providers [ TwoFactorProviderType . WebAuthn ] = provider ;
2018-10-08 14:38:11 -04:00
user . SetTwoFactorProviders ( providers ) ;
2021-03-22 23:21:43 +01:00
await UpdateTwoFactorProviderAsync ( user , TwoFactorProviderType . WebAuthn ) ;
2017-06-21 22:33:45 -04:00
return true ;
}
2017-07-01 23:20:19 -04:00
public async Task SendEmailVerificationAsync ( User user )
{
2020-03-27 14:36:37 -04:00
if ( user . EmailVerified )
2017-07-01 23:20:19 -04:00
{
2017-07-05 15:35:46 -04:00
throw new BadRequestException ( "Email already verified." ) ;
2017-07-01 23:20:19 -04:00
}
var token = await base . GenerateEmailConfirmationTokenAsync ( user ) ;
await _mailService . SendVerifyEmailEmailAsync ( user . Email , user . Id , token ) ;
}
2015-12-08 22:57:38 -05:00
public async Task InitiateEmailChangeAsync ( User user , string newEmail )
{
var existingUser = await _userRepository . GetByEmailAsync ( newEmail ) ;
2020-03-27 14:36:37 -04:00
if ( existingUser ! = null )
2015-12-08 22:57:38 -05:00
{
await _mailService . SendChangeEmailAlreadyExistsEmailAsync ( user . Email , newEmail ) ;
return ;
}
var token = await base . GenerateChangeEmailTokenAsync ( user , newEmail ) ;
await _mailService . SendChangeEmailEmailAsync ( newEmail , token ) ;
}
2017-04-17 14:53:07 -04: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 )
2015-12-08 22:57:38 -05:00
{
var verifyPasswordResult = _passwordHasher . VerifyHashedPassword ( user , user . MasterPassword , masterPassword ) ;
2020-03-27 14:36:37 -04:00
if ( verifyPasswordResult = = PasswordVerificationResult . Failed )
2015-12-08 22:57:38 -05:00
{
return IdentityResult . Failed ( _identityErrorDescriber . PasswordMismatch ( ) ) ;
}
2020-03-27 14:36:37 -04:00
if ( ! await base . VerifyUserTokenAsync ( user , _identityOptions . Tokens . ChangeEmailTokenProvider ,
2017-01-24 22:48:33 -05:00
GetChangeEmailTokenPurpose ( newEmail ) , token ) )
2015-12-08 22:57:38 -05:00
{
return IdentityResult . Failed ( _identityErrorDescriber . InvalidToken ( ) ) ;
}
var existingUser = await _userRepository . GetByEmailAsync ( newEmail ) ;
2020-03-27 14:36:37 -04:00
if ( existingUser ! = null & & existingUser . Id ! = user . Id )
2015-12-08 22:57:38 -05:00
{
return IdentityResult . Failed ( _identityErrorDescriber . DuplicateEmail ( newEmail ) ) ;
}
2016-02-21 00:15:17 -05:00
var result = await UpdatePasswordHash ( user , newMasterPassword ) ;
2020-03-27 14:36:37 -04:00
if ( ! result . Succeeded )
2016-02-21 00:15:17 -05:00
{
return result ;
}
2015-12-08 22:57:38 -05:00
2017-05-31 09:54:32 -04:00
user . Key = key ;
2016-02-21 00:15:17 -05:00
user . Email = newEmail ;
2016-02-21 00:50:53 -05:00
user . EmailVerified = true ;
2017-01-14 10:02:37 -05:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
2017-05-31 09:54:32 -04:00
await _userRepository . ReplaceAsync ( user ) ;
2018-08-28 08:29:54 -04:00
await _pushService . PushLogOutAsync ( user . Id ) ;
2016-10-05 22:03:02 -04:00
2015-12-08 22:57:38 -05:00
return IdentityResult . Success ;
}
2016-02-21 00:15:17 -05:00
public override Task < IdentityResult > ChangePasswordAsync ( User user , string masterPassword , string newMasterPassword )
2015-12-08 22:57:38 -05:00
{
throw new NotImplementedException ( ) ;
}
2017-04-27 17:28:39 -04:00
public async Task < IdentityResult > ChangePasswordAsync ( User user , string masterPassword , string newMasterPassword ,
2017-05-31 09:54:32 -04:00
string key )
2015-12-08 22:57:38 -05:00
{
2020-03-27 14:36:37 -04:00
if ( user = = null )
2015-12-08 22:57:38 -05:00
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2020-03-27 14:36:37 -04:00
if ( await CheckPasswordAsync ( user , masterPassword ) )
2015-12-08 22:57:38 -05:00
{
2016-02-21 00:15:17 -05:00
var result = await UpdatePasswordHash ( user , newMasterPassword ) ;
2020-03-27 14:36:37 -04:00
if ( ! result . Succeeded )
2015-12-08 22:57:38 -05:00
{
return result ;
}
2016-02-21 01:10:31 -05:00
2017-01-14 10:02:37 -05:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
2017-05-31 09:54:32 -04:00
user . Key = key ;
2017-12-01 10:07:14 -05:00
2017-05-31 09:54:32 -04:00
await _userRepository . ReplaceAsync ( user ) ;
2017-12-01 14:06:16 -05:00
await _eventService . LogUserEventAsync ( user . Id , EventType . User_ChangedPassword ) ;
2018-08-28 08:29:54 -04:00
await _pushService . PushLogOutAsync ( user . Id ) ;
2017-05-31 09:54:32 -04:00
return IdentityResult . Success ;
}
Logger . LogWarning ( "Change password failed for user {userId}." , user . Id ) ;
return IdentityResult . Failed ( _identityErrorDescriber . PasswordMismatch ( ) ) ;
}
2020-08-13 17:30:10 -04:00
2020-10-13 15:00:33 -05:00
public async Task < IdentityResult > SetPasswordAsync ( User user , string masterPassword , string key ,
string orgIdentifier = null )
2020-08-12 17:03:09 -04:00
{
if ( user = = null )
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
if ( ! string . IsNullOrWhiteSpace ( user . MasterPassword ) )
{
Logger . LogWarning ( "Change password failed for user {userId} - already has password." , user . Id ) ;
return IdentityResult . Failed ( _identityErrorDescriber . UserAlreadyHasPassword ( ) ) ;
}
2020-08-13 17:30:10 -04:00
2020-09-14 14:27:30 -05:00
var result = await UpdatePasswordHash ( user , masterPassword , true , false ) ;
2020-08-12 17:03:09 -04:00
if ( ! result . Succeeded )
{
return result ;
}
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . Key = key ;
await _userRepository . ReplaceAsync ( user ) ;
await _eventService . LogUserEventAsync ( user . Id , EventType . User_ChangedPassword ) ;
2020-10-13 15:00:33 -05:00
if ( ! string . IsNullOrWhiteSpace ( orgIdentifier ) )
{
await _organizationService . AcceptUserAsync ( orgIdentifier , user , this ) ;
}
2020-08-12 17:03:09 -04:00
return IdentityResult . Success ;
}
2021-04-20 16:58:57 -05:00
2021-05-19 09:40:32 -05:00
public async Task < IdentityResult > AdminResetPasswordAsync ( OrganizationUserType callingUserType , Guid orgId , Guid id , string newMasterPassword , string key )
2021-04-20 16:58:57 -05:00
{
2021-05-19 09:40:32 -05:00
// Org must be able to use reset password
var org = await _organizationRepository . GetByIdAsync ( orgId ) ;
if ( org = = null | | ! org . UseResetPassword )
{
throw new BadRequestException ( "Organization does not allow password reset." ) ;
}
// Enterprise policy must be enabled
var resetPasswordPolicy =
await _policyRepository . GetByOrganizationIdTypeAsync ( orgId , PolicyType . ResetPassword ) ;
if ( resetPasswordPolicy = = null | | ! resetPasswordPolicy . Enabled )
{
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" ) ;
}
// Calling User must be of higher/equal user type to reset user's password
var canAdjustPassword = false ;
switch ( callingUserType )
{
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 )
{
throw new NotFoundException ( ) ;
}
2021-04-20 16:58:57 -05:00
var result = await UpdatePasswordHash ( user , newMasterPassword ) ;
if ( ! result . Succeeded )
{
return result ;
}
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . Key = key ;
2021-08-31 14:23:06 -05:00
user . ForcePasswordReset = true ;
2021-04-20 16:58:57 -05:00
await _userRepository . ReplaceAsync ( user ) ;
2021-05-26 16:54:25 -05:00
await _mailService . SendAdminResetPasswordEmailAsync ( user . Email , user . Name ? ? user . Email , org . Name ) ;
2021-05-26 15:51:54 -05:00
await _eventService . LogOrganizationUserEventAsync ( orgUser , EventType . OrganizationUser_AdminResetPassword ) ;
2021-04-20 16:58:57 -05:00
await _pushService . PushLogOutAsync ( user . Id ) ;
return IdentityResult . Success ;
}
2021-07-22 09:20:14 -05:00
2021-08-05 13:00:24 -05:00
public async Task < IdentityResult > UpdateTempPasswordAsync ( User user , string newMasterPassword , string key , string hint )
2021-07-22 09:20:14 -05:00
{
if ( ! user . ForcePasswordReset )
{
throw new BadRequestException ( "User does not have a temporary password to update." ) ;
}
var result = await UpdatePasswordHash ( user , newMasterPassword ) ;
if ( ! result . Succeeded )
{
return result ;
}
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . ForcePasswordReset = false ;
user . Key = key ;
2021-08-05 13:00:24 -05:00
user . MasterPasswordHint = hint ;
2021-07-22 09:20:14 -05:00
await _userRepository . ReplaceAsync ( user ) ;
await _mailService . SendUpdatedTempPasswordEmailAsync ( user . Email , user . Name ? ? user . Email ) ;
await _eventService . LogUserEventAsync ( user . Id , EventType . User_UpdatedTempPassword ) ;
await _pushService . PushLogOutAsync ( user . Id ) ;
return IdentityResult . Success ;
}
2017-05-31 09:54:32 -04:00
2018-08-14 15:30:04 -04:00
public async Task < IdentityResult > ChangeKdfAsync ( User user , string masterPassword , string newMasterPassword ,
string key , KdfType kdf , int kdfIterations )
{
2020-03-27 14:36:37 -04:00
if ( user = = null )
2018-08-14 15:30:04 -04:00
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2020-03-27 14:36:37 -04:00
if ( await CheckPasswordAsync ( user , masterPassword ) )
2018-08-14 15:30:04 -04:00
{
var result = await UpdatePasswordHash ( user , newMasterPassword ) ;
2020-03-27 14:36:37 -04:00
if ( ! result . Succeeded )
2018-08-14 15:30:04 -04:00
{
return result ;
}
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . Key = key ;
user . Kdf = kdf ;
user . KdfIterations = kdfIterations ;
await _userRepository . ReplaceAsync ( user ) ;
2018-08-28 08:29:54 -04:00
await _pushService . PushLogOutAsync ( user . Id ) ;
2018-08-14 15:30:04 -04:00
return IdentityResult . Success ;
}
Logger . LogWarning ( "Change KDF failed for user {userId}." , user . Id ) ;
return IdentityResult . Failed ( _identityErrorDescriber . PasswordMismatch ( ) ) ;
}
2017-05-31 09:54:32 -04:00
public async Task < IdentityResult > UpdateKeyAsync ( User user , string masterPassword , string key , string privateKey ,
2021-07-02 06:27:03 +10:00
IEnumerable < Cipher > ciphers , IEnumerable < Folder > folders , IEnumerable < Send > sends )
2017-05-31 09:54:32 -04:00
{
2020-03-27 14:36:37 -04:00
if ( user = = null )
2017-05-31 09:54:32 -04:00
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2020-03-27 14:36:37 -04:00
if ( await CheckPasswordAsync ( user , masterPassword ) )
2017-05-31 09:54:32 -04:00
{
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . SecurityStamp = Guid . NewGuid ( ) . ToString ( ) ;
user . Key = key ;
2017-04-17 14:53:07 -04:00
user . PrivateKey = privateKey ;
2021-07-02 06:27:03 +10:00
if ( ciphers . Any ( ) | | folders . Any ( ) | | sends . Any ( ) )
2016-10-05 22:03:02 -04:00
{
2021-07-02 06:27:03 +10:00
await _cipherRepository . UpdateUserKeysAndCiphersAsync ( user , ciphers , folders , sends ) ;
2016-10-05 22:03:02 -04:00
}
else
{
await _userRepository . ReplaceAsync ( user ) ;
}
2018-08-28 08:29:54 -04:00
await _pushService . PushLogOutAsync ( user . Id ) ;
2015-12-08 22:57:38 -05:00
return IdentityResult . Success ;
}
2018-08-14 15:30:04 -04:00
Logger . LogWarning ( "Update key failed for user {userId}." , user . Id ) ;
2015-12-08 22:57:38 -05:00
return IdentityResult . Failed ( _identityErrorDescriber . PasswordMismatch ( ) ) ;
}
2016-02-21 00:15:17 -05:00
public async Task < IdentityResult > RefreshSecurityStampAsync ( User user , string masterPassword )
2015-12-08 22:57:38 -05:00
{
2020-03-27 14:36:37 -04:00
if ( user = = null )
2015-12-08 22:57:38 -05:00
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2020-03-27 14:36:37 -04:00
if ( await CheckPasswordAsync ( user , masterPassword ) )
2015-12-08 22:57:38 -05:00
{
var result = await base . UpdateSecurityStampAsync ( user ) ;
2020-03-27 14:36:37 -04:00
if ( ! result . Succeeded )
2015-12-08 22:57:38 -05:00
{
return result ;
}
2016-02-21 01:10:31 -05:00
await SaveUserAsync ( user ) ;
2018-08-28 08:29:54 -04:00
await _pushService . PushLogOutAsync ( user . Id ) ;
2015-12-08 22:57:38 -05:00
return IdentityResult . Success ;
}
Logger . LogWarning ( "Refresh security stamp failed for user {userId}." , user . Id ) ;
return IdentityResult . Failed ( _identityErrorDescriber . PasswordMismatch ( ) ) ;
}
2021-03-22 23:21:43 +01:00
public async Task UpdateTwoFactorProviderAsync ( User user , TwoFactorProviderType type , bool setEnabled = true )
2017-06-07 14:14:34 -04:00
{
2021-03-22 23:21:43 +01:00
SetTwoFactorProvider ( user , type , setEnabled ) ;
2017-06-19 22:08:10 -04:00
await SaveUserAsync ( user ) ;
2018-10-09 15:59:07 -04:00
await _eventService . LogUserEventAsync ( user . Id , EventType . User_Updated2fa ) ;
2017-06-19 22:08:10 -04:00
}
2020-02-19 14:56:16 -05:00
public async Task DisableTwoFactorProviderAsync ( User user , TwoFactorProviderType type ,
IOrganizationService organizationService )
2017-06-19 22:08:10 -04:00
{
var providers = user . GetTwoFactorProviders ( ) ;
2020-03-27 14:36:37 -04:00
if ( ! providers ? . ContainsKey ( type ) ? ? true )
2017-06-19 22:08:10 -04:00
{
return ;
}
2021-05-05 16:14:49 +02:00
// Since the user can no longer directly manipulate U2F tokens, we should
// disable them when the user disables WebAuthn.
if ( type = = TwoFactorProviderType . WebAuthn )
{
providers . Remove ( TwoFactorProviderType . U2f ) ;
}
2017-06-19 22:08:10 -04:00
providers . Remove ( type ) ;
user . SetTwoFactorProviders ( providers ) ;
2016-02-21 01:10:31 -05:00
await SaveUserAsync ( user ) ;
2017-12-01 14:06:16 -05:00
await _eventService . LogUserEventAsync ( user . Id , EventType . User_Disabled2fa ) ;
2020-02-19 14:56:16 -05:00
2020-03-27 14:36:37 -04:00
if ( ! await TwoFactorIsEnabledAsync ( user ) )
2020-02-19 14:56:16 -05:00
{
2020-02-28 10:23:19 -05:00
await CheckPoliciesOnTwoFactorRemovalAsync ( user , organizationService ) ;
2020-02-19 14:56:16 -05:00
}
2015-12-08 22:57:38 -05:00
}
2020-02-28 10:23:19 -05:00
public async Task < bool > RecoverTwoFactorAsync ( string email , string masterPassword , string recoveryCode ,
IOrganizationService organizationService )
2016-11-14 23:32:15 -05:00
{
var user = await _userRepository . GetByEmailAsync ( email ) ;
2020-03-27 14:36:37 -04:00
if ( user = = null )
2016-11-14 23:32:15 -05:00
{
// No user exists. Do we want to send an email telling them this in the future?
return false ;
}
2020-03-27 14:36:37 -04:00
if ( ! await CheckPasswordAsync ( user , masterPassword ) )
2016-11-14 23:32:15 -05:00
{
return false ;
}
2020-03-27 14:36:37 -04:00
if ( string . Compare ( user . TwoFactorRecoveryCode , recoveryCode , true ) ! = 0 )
2016-11-14 23:32:15 -05:00
{
return false ;
}
2017-06-19 22:25:19 -04:00
user . TwoFactorProviders = null ;
2017-08-11 17:06:31 -04:00
user . TwoFactorRecoveryCode = CoreHelpers . SecureRandomString ( 32 , upper : false , special : false ) ;
2016-11-14 23:32:15 -05:00
await SaveUserAsync ( user ) ;
2019-12-23 15:26:39 -05: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 ) ;
2020-02-28 10:23:19 -05:00
await CheckPoliciesOnTwoFactorRemovalAsync ( user , organizationService ) ;
2016-11-14 23:32:15 -05:00
return true ;
}
2019-08-09 23:56:26 -04:00
public async Task < Tuple < bool , string > > SignUpPremiumAsync ( User user , string paymentToken ,
2020-06-17 19:49:27 -04:00
PaymentMethodType paymentMethodType , short additionalStorageGb , UserLicense license ,
2020-06-18 10:41:55 -04:00
TaxInfo taxInfo )
2017-07-06 14:55:58 -04:00
{
2020-03-27 14:36:37 -04:00
if ( user . Premium )
2017-07-06 14:55:58 -04:00
{
throw new BadRequestException ( "Already a premium user." ) ;
}
2020-03-27 14:36:37 -04:00
if ( additionalStorageGb < 0 )
2019-01-31 14:25:46 -05:00
{
throw new BadRequestException ( "You can't subtract storage!" ) ;
}
2020-03-27 14:36:37 -04:00
if ( ( paymentMethodType = = PaymentMethodType . GoogleInApp | |
2019-09-18 10:52:53 -04:00
paymentMethodType = = PaymentMethodType . AppleInApp ) & & additionalStorageGb > 0 )
{
throw new BadRequestException ( "You cannot add storage with this payment method." ) ;
}
2019-08-09 23:56:26 -04:00
string paymentIntentClientSecret = null ;
2017-07-28 12:09:12 -04:00
IPaymentService paymentService = null ;
2020-03-27 14:36:37 -04:00
if ( _globalSettings . SelfHosted )
2017-07-28 12:09:12 -04:00
{
2020-03-27 14:36:37 -04:00
if ( license = = null | | ! _licenseService . VerifyLicense ( license ) )
2017-08-11 17:06:31 -04:00
{
throw new BadRequestException ( "Invalid license." ) ;
}
2020-03-27 14:36:37 -04:00
if ( ! license . CanUse ( user ) )
2017-08-16 15:43:11 -04:00
{
throw new BadRequestException ( "This license is not valid for this user." ) ;
}
2017-08-11 22:55:25 -04:00
var dir = $"{_globalSettings.LicenseDirectory}/user" ;
Directory . CreateDirectory ( dir ) ;
File . WriteAllText ( $"{dir}/{user.Id}.json" , JsonConvert . SerializeObject ( license , Formatting . Indented ) ) ;
2017-08-11 17:06:31 -04:00
}
2019-02-20 23:54:27 -05:00
else
2017-08-11 17:06:31 -04:00
{
2019-08-09 23:56:26 -04:00
paymentIntentClientSecret = await _paymentService . PurchasePremiumAsync ( user , paymentMethodType ,
2020-06-18 10:41:55 -04:00
paymentToken , additionalStorageGb , taxInfo ) ;
2017-07-28 12:09:12 -04:00
}
2017-07-06 14:55:58 -04:00
user . Premium = true ;
user . RevisionDate = DateTime . UtcNow ;
2020-03-27 14:36:37 -04:00
if ( _globalSettings . SelfHosted )
2017-08-11 22:55:25 -04:00
{
2017-08-11 23:08:41 -04:00
user . MaxStorageGb = 10240 ; // 10 TB
2017-08-11 22:55:25 -04:00
user . LicenseKey = license . LicenseKey ;
2017-08-12 22:16:42 -04:00
user . PremiumExpirationDate = license . Expires ;
2017-08-11 22:55:25 -04:00
}
else
{
user . MaxStorageGb = ( short ) ( 1 + additionalStorageGb ) ;
2017-08-11 23:26:38 -04:00
user . LicenseKey = CoreHelpers . SecureRandomString ( 20 ) ;
2017-08-11 22:55:25 -04:00
}
2017-07-06 14:55:58 -04:00
try
{
await SaveUserAsync ( user ) ;
2018-08-28 08:29:54 -04:00
await _pushService . PushSyncVaultAsync ( user . Id ) ;
2020-07-07 12:01:34 -04:00
await _referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . UpgradePlan , user )
{
Storage = user . MaxStorageGb ,
PlanName = PremiumPlanId ,
} ) ;
2017-07-06 14:55:58 -04:00
}
2020-08-13 17:30:10 -04:00
catch when ( ! _globalSettings . SelfHosted )
2017-07-06 14:55:58 -04:00
{
2017-08-11 22:55:25 -04:00
await paymentService . CancelAndRecoverChargesAsync ( user ) ;
2017-07-06 14:55:58 -04:00
throw ;
}
2019-08-09 23:56:26 -04:00
return new Tuple < bool , string > ( string . IsNullOrWhiteSpace ( paymentIntentClientSecret ) ,
paymentIntentClientSecret ) ;
2017-07-06 14:55:58 -04:00
}
2019-09-19 08:46:26 -04:00
public async Task IapCheckAsync ( User user , PaymentMethodType paymentMethodType )
{
2020-03-27 14:36:37 -04:00
if ( paymentMethodType ! = PaymentMethodType . AppleInApp )
2019-09-19 08:46:26 -04:00
{
throw new BadRequestException ( "Payment method not supported for in-app purchases." ) ;
}
2020-03-27 14:36:37 -04:00
if ( user . Premium )
2019-09-19 08:46:26 -04:00
{
throw new BadRequestException ( "Already a premium user." ) ;
}
2020-03-27 14:36:37 -04:00
if ( ! string . IsNullOrWhiteSpace ( user . GatewayCustomerId ) )
2019-09-19 08:46:26 -04:00
{
var customerService = new Stripe . CustomerService ( ) ;
var customer = await customerService . GetAsync ( user . GatewayCustomerId ) ;
2020-03-27 14:36:37 -04:00
if ( customer ! = null & & customer . Balance ! = 0 )
2019-09-19 08:46:26 -04:00
{
throw new BadRequestException ( "Customer balance cannot exist when using in-app purchases." ) ;
}
}
}
2017-08-14 12:08:57 -04:00
public async Task UpdateLicenseAsync ( User user , UserLicense license )
{
2020-03-27 14:36:37 -04:00
if ( ! _globalSettings . SelfHosted )
2017-08-14 12:08:57 -04:00
{
throw new InvalidOperationException ( "Licenses require self hosting." ) ;
}
2020-03-27 14:36:37 -04:00
if ( license = = null | | ! _licenseService . VerifyLicense ( license ) )
2017-08-14 12:08:57 -04:00
{
throw new BadRequestException ( "Invalid license." ) ;
}
2020-03-27 14:36:37 -04:00
if ( ! license . CanUse ( user ) )
2017-08-16 15:43:11 -04:00
{
throw new BadRequestException ( "This license is not valid for this user." ) ;
}
2017-08-14 12:08:57 -04:00
var dir = $"{_globalSettings.LicenseDirectory}/user" ;
Directory . CreateDirectory ( dir ) ;
File . WriteAllText ( $"{dir}/{user.Id}.json" , JsonConvert . SerializeObject ( license , Formatting . Indented ) ) ;
2017-08-16 15:43:11 -04:00
user . Premium = license . Premium ;
2017-08-14 12:08:57 -04:00
user . RevisionDate = DateTime . UtcNow ;
2017-08-16 15:43:11 -04:00
user . MaxStorageGb = _globalSettings . SelfHosted ? 10240 : license . MaxStorageGb ; // 10 TB
2017-08-14 12:08:57 -04:00
user . LicenseKey = license . LicenseKey ;
user . PremiumExpirationDate = license . Expires ;
await SaveUserAsync ( user ) ;
}
2019-08-10 12:59:32 -04:00
public async Task < string > AdjustStorageAsync ( User user , short storageAdjustmentGb )
2017-07-06 14:55:58 -04:00
{
2020-03-27 14:36:37 -04:00
if ( user = = null )
2017-07-06 14:55:58 -04:00
{
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2020-03-27 14:36:37 -04:00
if ( ! user . Premium )
2017-07-06 14:55:58 -04:00
{
throw new BadRequestException ( "Not a premium user." ) ;
}
2019-02-19 17:13:21 -05:00
2019-08-10 12:59:32 -04:00
var secret = await BillingHelpers . AdjustStorageAsync ( _paymentService , user , storageAdjustmentGb ,
StoragePlanId ) ;
2020-07-07 12:01:34 -04:00
await _referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . AdjustStorage , user )
{
Storage = storageAdjustmentGb ,
PlanName = StoragePlanId ,
} ) ;
2017-07-06 14:55:58 -04:00
await SaveUserAsync ( user ) ;
2019-08-10 12:59:32 -04:00
return secret ;
2017-07-06 14:55:58 -04:00
}
2020-06-18 10:41:55 -04:00
public async Task ReplacePaymentMethodAsync ( User user , string paymentToken , PaymentMethodType paymentMethodType , TaxInfo taxInfo )
2017-07-06 14:55:58 -04:00
{
2020-03-27 14:36:37 -04:00
if ( paymentToken . StartsWith ( "btok_" ) )
2017-08-13 00:33:37 -04:00
{
throw new BadRequestException ( "Invalid token." ) ;
}
2020-06-18 10:41:55 -04:00
var updated = await _paymentService . UpdatePaymentMethodAsync ( user , paymentMethodType , paymentToken , taxInfo : taxInfo ) ;
2020-03-27 14:36:37 -04:00
if ( updated )
2017-07-06 14:55:58 -04:00
{
await SaveUserAsync ( user ) ;
}
}
2019-09-20 13:45:47 -04:00
public async Task CancelPremiumAsync ( User user , bool? endOfPeriod = null , bool accountDelete = false )
2017-07-06 14:55:58 -04:00
{
2018-12-31 13:34:02 -05:00
var eop = endOfPeriod . GetValueOrDefault ( true ) ;
2020-03-27 14:36:37 -04:00
if ( ! endOfPeriod . HasValue & & user . PremiumExpirationDate . HasValue & &
2018-12-31 13:34:02 -05:00
user . PremiumExpirationDate . Value < DateTime . UtcNow )
{
eop = false ;
}
2019-09-20 13:45:47 -04:00
await _paymentService . CancelSubscriptionAsync ( user , eop , accountDelete ) ;
2020-07-07 12:01:34 -04:00
await _referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . CancelSubscription , user )
{
EndOfPeriod = eop ,
} ) ;
2017-07-06 14:55:58 -04:00
}
public async Task ReinstatePremiumAsync ( User user )
{
2019-02-08 23:53:09 -05:00
await _paymentService . ReinstateSubscriptionAsync ( user ) ;
2020-07-07 12:01:34 -04:00
await _referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . ReinstateSubscription , user ) ) ;
2017-07-06 14:55:58 -04:00
}
2019-08-09 23:56:26 -04:00
public async Task EnablePremiumAsync ( Guid userId , DateTime ? expirationDate )
{
var user = await _userRepository . GetByIdAsync ( userId ) ;
await EnablePremiumAsync ( user , expirationDate ) ;
}
public async Task EnablePremiumAsync ( User user , DateTime ? expirationDate )
{
2020-03-27 14:36:37 -04:00
if ( user ! = null & & ! user . Premium & & user . Gateway . HasValue )
2019-08-09 23:56:26 -04:00
{
user . Premium = true ;
user . PremiumExpirationDate = expirationDate ;
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
}
}
2017-08-12 22:16:42 -04:00
public async Task DisablePremiumAsync ( Guid userId , DateTime ? expirationDate )
2017-07-25 09:04:22 -04:00
{
var user = await _userRepository . GetByIdAsync ( userId ) ;
2017-08-16 17:08:20 -04:00
await DisablePremiumAsync ( user , expirationDate ) ;
}
public async Task DisablePremiumAsync ( User user , DateTime ? expirationDate )
{
2020-03-27 14:36:37 -04:00
if ( user ! = null & & user . Premium )
2017-07-25 09:04:22 -04:00
{
user . Premium = false ;
2017-08-12 22:16:42 -04:00
user . PremiumExpirationDate = expirationDate ;
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
}
}
public async Task UpdatePremiumExpirationAsync ( Guid userId , DateTime ? expirationDate )
{
var user = await _userRepository . GetByIdAsync ( userId ) ;
2020-03-27 14:36:37 -04:00
if ( user ! = null )
2017-08-12 22:16:42 -04:00
{
user . PremiumExpirationDate = expirationDate ;
2017-07-25 09:04:22 -04:00
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
}
}
2020-08-18 17:00:21 -04:00
public async Task < UserLicense > GenerateLicenseAsync ( User user , SubscriptionInfo subscriptionInfo = null ,
int? version = null )
2017-08-30 11:23:55 -04:00
{
2020-03-27 14:36:37 -04:00
if ( user = = null )
2017-08-30 11:23:55 -04:00
{
throw new NotFoundException ( ) ;
}
2020-03-27 14:36:37 -04:00
if ( subscriptionInfo = = null & & user . Gateway ! = null )
2017-08-30 11:23:55 -04:00
{
2019-02-18 15:40:47 -05:00
subscriptionInfo = await _paymentService . GetSubscriptionAsync ( user ) ;
2017-08-30 11:23:55 -04:00
}
2019-02-18 15:40:47 -05:00
return subscriptionInfo = = null ? new UserLicense ( user , _licenseService ) :
new UserLicense ( user , subscriptionInfo , _licenseService ) ;
2017-08-30 11:23:55 -04:00
}
2018-04-17 08:10:17 -04:00
public override async Task < bool > CheckPasswordAsync ( User user , string password )
{
2020-03-27 14:36:37 -04:00
if ( user = = null )
2018-04-17 08:10:17 -04:00
{
return false ;
}
var result = await base . VerifyPasswordAsync ( Store as IUserPasswordStore < User > , user , password ) ;
2020-03-27 14:36:37 -04:00
if ( result = = PasswordVerificationResult . SuccessRehashNeeded )
2018-04-17 08:10:17 -04:00
{
await UpdatePasswordHash ( user , password , false , false ) ;
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
}
var success = result ! = PasswordVerificationResult . Failed ;
2020-03-27 14:36:37 -04:00
if ( ! success )
2018-04-17 08:10:17 -04:00
{
Logger . LogWarning ( 0 , "Invalid password for user {userId}." , user . Id ) ;
}
return success ;
}
2018-12-19 11:48:36 -05:00
public async Task < bool > CanAccessPremium ( ITwoFactorProvidersUser user )
2018-08-28 16:23:58 -04:00
{
2018-12-19 11:48:36 -05:00
var userId = user . GetUserId ( ) ;
2020-03-27 14:36:37 -04:00
if ( ! userId . HasValue )
2018-12-19 11:48:36 -05:00
{
return false ;
}
2020-03-27 14:36:37 -04:00
if ( user . GetPremium ( ) )
2018-08-28 16:23:58 -04:00
{
return true ;
}
2018-12-19 11:48:36 -05:00
var orgs = await _currentContext . OrganizationMembershipAsync ( _organizationUserRepository , userId . Value ) ;
2020-03-27 14:36:37 -04:00
if ( ! orgs . Any ( ) )
2018-08-28 16:23:58 -04:00
{
return false ;
}
var orgAbilities = await _applicationCacheService . GetOrganizationAbilitiesAsync ( ) ;
2018-08-31 17:05:27 -04:00
return orgs . Any ( o = > orgAbilities . ContainsKey ( o . Id ) & &
2018-08-29 13:51:24 -04:00
orgAbilities [ o . Id ] . UsersGetPremium & & orgAbilities [ o . Id ] . Enabled ) ;
2018-08-28 16:23:58 -04:00
}
2018-12-19 11:48:36 -05:00
public async Task < bool > TwoFactorIsEnabledAsync ( ITwoFactorProvidersUser user )
2018-12-19 10:47:53 -05:00
{
var providers = user . GetTwoFactorProviders ( ) ;
2020-03-27 14:36:37 -04:00
if ( providers = = null )
2018-12-19 10:47:53 -05:00
{
return false ;
}
2020-03-27 14:36:37 -04:00
foreach ( var p in providers )
2018-12-19 10:47:53 -05:00
{
2020-03-27 14:36:37 -04:00
if ( p . Value ? . Enabled ? ? false )
2018-12-19 10:47:53 -05:00
{
2020-03-27 14:36:37 -04:00
if ( ! TwoFactorProvider . RequiresPremium ( p . Key ) )
2018-12-19 10:47:53 -05:00
{
return true ;
}
2020-03-27 14:36:37 -04:00
if ( await CanAccessPremium ( user ) )
2018-12-19 10:47:53 -05:00
{
return true ;
}
}
}
return false ;
}
2018-12-19 11:48:36 -05:00
public async Task < bool > TwoFactorProviderIsEnabledAsync ( TwoFactorProviderType provider , ITwoFactorProvidersUser user )
2018-12-19 10:47:53 -05:00
{
var providers = user . GetTwoFactorProviders ( ) ;
2020-03-27 14:36:37 -04:00
if ( providers = = null | | ! providers . ContainsKey ( provider ) | | ! providers [ provider ] . Enabled )
2018-12-19 10:47:53 -05:00
{
return false ;
}
2020-03-27 14:36:37 -04:00
if ( ! TwoFactorProvider . RequiresPremium ( provider ) )
2018-12-19 10:47:53 -05:00
{
return true ;
}
return await CanAccessPremium ( user ) ;
}
2020-08-26 14:12:04 -04:00
//TODO refactor this to use the below method and enum
2020-05-12 15:36:33 -04:00
public async Task < string > GenerateEnterprisePortalSignInTokenAsync ( User user )
{
var token = await GenerateUserTokenAsync ( user , Options . Tokens . PasswordResetTokenProvider ,
"EnterprisePortalTokenSignIn" ) ;
return token ;
}
2020-08-26 14:12:04 -04:00
public async Task < string > GenerateSignInTokenAsync ( User user , string purpose )
{
var token = await GenerateUserTokenAsync ( user , Options . Tokens . PasswordResetTokenProvider ,
purpose ) ;
return token ;
}
2018-04-17 08:10:17 -04:00
private async Task < IdentityResult > UpdatePasswordHash ( User user , string newPassword ,
bool validatePassword = true , bool refreshStamp = true )
2015-12-08 22:57:38 -05:00
{
2020-03-27 14:36:37 -04:00
if ( validatePassword )
2015-12-08 22:57:38 -05:00
{
var validate = await ValidatePasswordInternal ( user , newPassword ) ;
2020-03-27 14:36:37 -04:00
if ( ! validate . Succeeded )
2015-12-08 22:57:38 -05:00
{
return validate ;
}
}
user . MasterPassword = _passwordHasher . HashPassword ( user , newPassword ) ;
2020-03-27 14:36:37 -04:00
if ( refreshStamp )
2018-04-17 08:10:17 -04:00
{
user . SecurityStamp = Guid . NewGuid ( ) . ToString ( ) ;
}
2015-12-08 22:57:38 -05:00
return IdentityResult . Success ;
}
private async Task < IdentityResult > ValidatePasswordInternal ( User user , string password )
{
var errors = new List < IdentityError > ( ) ;
2020-03-27 14:36:37 -04:00
foreach ( var v in _passwordValidators )
2015-12-08 22:57:38 -05:00
{
var result = await v . ValidateAsync ( this , user , password ) ;
2020-03-27 14:36:37 -04:00
if ( ! result . Succeeded )
2015-12-08 22:57:38 -05:00
{
errors . AddRange ( result . Errors ) ;
}
}
2020-03-27 14:36:37 -04:00
if ( errors . Count > 0 )
2015-12-08 22:57:38 -05:00
{
2017-04-27 17:28:39 -04:00
Logger . LogWarning ( "User {userId} password validation failed: {errors}." , await GetUserIdAsync ( user ) ,
2017-01-24 22:48:33 -05:00
string . Join ( ";" , errors . Select ( e = > e . Code ) ) ) ;
2015-12-08 22:57:38 -05:00
return IdentityResult . Failed ( errors . ToArray ( ) ) ;
}
return IdentityResult . Success ;
}
2020-02-28 09:14:33 -05:00
2021-03-22 23:21:43 +01:00
public void SetTwoFactorProvider ( User user , TwoFactorProviderType type , bool setEnabled = true )
2020-02-28 09:14:33 -05:00
{
var providers = user . GetTwoFactorProviders ( ) ;
2020-03-27 14:36:37 -04:00
if ( ! providers ? . ContainsKey ( type ) ? ? true )
2020-02-28 09:14:33 -05:00
{
return ;
}
2021-03-22 23:21:43 +01:00
if ( setEnabled )
{
providers [ type ] . Enabled = true ;
}
2020-02-28 09:14:33 -05:00
user . SetTwoFactorProviders ( providers ) ;
2020-03-27 14:36:37 -04:00
if ( string . IsNullOrWhiteSpace ( user . TwoFactorRecoveryCode ) )
2020-02-28 09:14:33 -05:00
{
user . TwoFactorRecoveryCode = CoreHelpers . SecureRandomString ( 32 , upper : false , special : false ) ;
}
}
2020-02-28 10:23:19 -05:00
private async Task CheckPoliciesOnTwoFactorRemovalAsync ( User user , IOrganizationService organizationService )
{
var policies = await _policyRepository . GetManyByUserIdAsync ( user . Id ) ;
var twoFactorPolicies = policies . Where ( p = > p . Type = = PolicyType . TwoFactorAuthentication & & p . Enabled ) ;
2020-03-27 14:36:37 -04:00
if ( twoFactorPolicies . Any ( ) )
2020-02-28 10:23:19 -05:00
{
var userOrgs = await _organizationUserRepository . GetManyByUserAsync ( user . Id ) ;
var ownerOrgs = userOrgs . Where ( o = > o . Type = = OrganizationUserType . Owner )
. Select ( o = > o . OrganizationId ) . ToHashSet ( ) ;
2020-03-27 14:36:37 -04:00
foreach ( var policy in twoFactorPolicies )
2020-02-28 10:23:19 -05:00
{
2020-03-27 14:36:37 -04:00
if ( ! ownerOrgs . Contains ( policy . OrganizationId ) )
2020-02-28 10:23:19 -05:00
{
await organizationService . DeleteUserAsync ( policy . OrganizationId , user . Id ) ;
var organization = await _organizationRepository . GetByIdAsync ( policy . OrganizationId ) ;
await _mailService . SendOrganizationUserRemovedForPolicyTwoStepEmailAsync (
organization . Name , user . Email ) ;
}
}
}
}
2020-07-15 12:38:45 -04:00
public override async Task < IdentityResult > ConfirmEmailAsync ( User user , string token )
{
var result = await base . ConfirmEmailAsync ( user , token ) ;
if ( result . Succeeded )
{
await _referenceEventService . RaiseEventAsync (
new ReferenceEvent ( ReferenceEventType . ConfirmEmailAddress , user ) ) ;
}
return result ;
}
2020-11-10 15:15:29 -05:00
public async Task RotateApiKeyAsync ( User user )
{
user . ApiKey = CoreHelpers . SecureRandomString ( 30 ) ;
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
}
2015-12-08 22:57:38 -05:00
}
}