2022-06-29 19:46:41 -04:00
using System.Security.Claims ;
2024-09-26 11:21:51 +01:00
using Bit.Core.AdminConsole.Entities ;
2023-11-23 07:07:37 +10:00
using Bit.Core.AdminConsole.Enums ;
2024-12-13 11:32:29 +00:00
using Bit.Core.AdminConsole.Models.Data ;
2024-10-16 10:33:00 +01:00
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces ;
2024-12-13 11:32:29 +00:00
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests ;
2023-10-27 03:38:29 +10:00
using Bit.Core.AdminConsole.Repositories ;
2023-11-23 07:07:37 +10:00
using Bit.Core.AdminConsole.Services ;
2023-04-14 13:25:56 -04:00
using Bit.Core.Auth.Enums ;
using Bit.Core.Auth.Models ;
2023-10-30 08:40:06 -05:00
using Bit.Core.Auth.Models.Business.Tokenables ;
2024-10-01 09:12:08 -04:00
using Bit.Core.Billing.Models.Sales ;
using Bit.Core.Billing.Services ;
2021-02-04 12:54:21 -06:00
using Bit.Core.Context ;
2022-01-11 10:40:51 +01:00
using Bit.Core.Entities ;
2021-11-09 16:37:32 +01:00
using Bit.Core.Enums ;
2017-07-01 23:20:19 -04:00
using Bit.Core.Exceptions ;
2021-11-09 16:37:32 +01:00
using Bit.Core.Models.Business ;
2024-12-13 11:32:29 +00:00
using Bit.Core.Models.Data.Organizations.OrganizationUsers ;
Auth/PM-3275 - Changes to support TDE User without MP being able to Set a Password + misc refactoring (#3242)
* PM-3275 - Add new GetMasterPasswordPolicy endpoint which will allow authenticated clients to get an enabled MP org policy if it exists for the purposes of enforcing those policy requirements when setting a password.
* PM-3275 - AccountsController.cs - PostSetPasswordAsync - (1) Convert UserService.setPasswordAsync into new SetInitialMasterPasswordCommand (2) Refactor SetInitialMasterPasswordCommand to only accept post SSO users who are in the invited state
(3) Add TODOs for more cleanup work and more commands
* PM-3275 - Update AccountsControllerTests.cs to add new SetInitialMasterPasswordCommand
* PM-3275 - UserService.cs - Remove non implemented ChangePasswordAsync method
* PM-3275 - The new SetInitialMasterPasswordCommand leveraged the OrganizationService.cs AcceptUserAsync method so while I was in here I converted the AcceptUserAsync methods into a new AcceptOrgUserCommand.cs and turned the private method which accepted an existing org user public for use in the SetInitialMasterPasswordCommand
* PM-3275 - Dotnet format
* PM-3275 - Test SetInitialMasterPasswordCommand
* Dotnet format
* PM-3275 - In process AcceptOrgUserCommandTests.cs
* PM-3275 - Migrate changes from AC-244 / #3199 over into new AcceptOrgUserCommand
* PM-3275 - AcceptOrgUserCommand.cs - create data protector specifically for this command
* PM-3275 - Add TODO for renaming / removing overloading of methods to improve readability / clarity
* PM-3275 - AcceptOrgUserCommand.cs - refactor AcceptOrgUserAsync by OrgId to retrieve orgUser with _organizationUserRepository.GetByOrganizationAsync which gets a single user instead of a collection
* PM-3275 - AcceptOrgUserCommand.cs - update name in TODO for evaluation later
* PM-3275 / PM-1196 - (1) Slightly refactor SsoEmail2faSessionTokenable to provide public static GetTokenLifeTime() method for testing (2) Add missed tests to SsoEmail2faSessionTokenable in preparation for building tests for new OrgUserInviteTokenable.cs
* PM-3275 / PM-1196 - Removing SsoEmail2faSessionTokenable.cs changes + tests as I've handled that separately in a new PR (#3270) for newly created task PM-3925
* PM-3275 - ExpiringTokenable.cs - add clarifying comments to help distinguish between the Valid property and the TokenIsValid method.
* PM-3275 - Create OrgUserInviteTokenable.cs and add tests in OrgUserInviteTokenableTests.cs
* PM-3275 - OrganizationService.cs - Refactor Org User Invite methods to use new OrgUserInviteTokenable instead of manual creation of a token
* PM-3275 - OrgUserInviteTokenable.cs - clarify backwards compat note
* PM-3275 - AcceptOrgUserCommand.cs - Add TODOs + minor name refactor
* PM-3275 - AcceptOrgUserCommand.cs - replace method overloading with more easily readable names.
* PM-3275 - AcceptOrgUserCommand.cs - Update ValidateOrgUserInviteToken to add new token validation while maintaining backwards compatibility for 1 release.
* dotnet format
* PM-3275 - AcceptOrgUserCommand.cs - Move private method below where it is used
* PM-3275 - ServiceCollectionExtensions.cs - Must register IDataProtectorTokenFactory<OrgUserInviteTokenable> for new tokenable
* PM-3275 - OrgUserInviteTokenable needed access to global settings to set its token lifetime to the _globalSettings.OrganizationInviteExpirationHours value. Creating a factory seemed the most straightforward way to encapsulate the desired creation logic. Unsure if in the correct location in ServiceCollectionExtensions.cs but will figure that out later.
* PM-3275 - In process work of creating AcceptOrgUserCommandTests.cs
* PM-3275 - Remove no longer relevant AcceptOrgUser tests from OrganizationServiceTests.cs
* PM-3275 - Register OrgUserInviteTokenableFactory alongside tokenizer
* PM-3275 - AcceptOrgUserCommandTests.cs - AcceptOrgUserAsync basic test suite completed.
* PM-3275 - AcceptOrgUserCommandTests.cs - tweak test names
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Remove old tests from OrganizationServiceTests as no longer needed to reference (2) Add summary for SetupCommonAcceptOrgUserMocks (3) Get AcceptOrgUserByToken_OldToken_AcceptsUserAndVerifiesEmail passing
* PM-3275 - Create interface for OrgUserInviteTokenableFactory b/c that's the right thing to do + enables test substitution
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Start work on AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail (2) Create and use SetupCommonAcceptOrgUserByTokenMocks() (3) Create generic FakeDataProtectorTokenFactory for tokenable testing
* PM-3275 - (1) Get AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail test passing (2) Move FakeDataProtectorTokenFactory to own file
* PM-3275 - AcceptOrgUserCommandTests.cs - Finish up tests for AcceptOrgUserByTokenAsync
* PM-3275 - Add pseudo section comments
* PM-3275 - Clean up unused params on AcceptOrgUserByToken_EmailMismatch_ThrowsBadRequest test
* PM-3275 - (1) Tests written for AcceptOrgUserByOrgSsoIdAsync (2) Refactor happy path assertions into helper function AssertValidAcceptedOrgUser to reduce code duplication
* PM-3275 - Finish up testing AcceptOrgUserCommandTests.cs by adding tests for AcceptOrgUserByOrgIdAsync
* PM-3275 - Tweaking test naming to ensure consistency.
* PM-3275 - Bugfix - OrgUserInviteTokenableFactory implementation required when declaring singleton service in ServiceCollectionExtensions.cs
* PM-3275 - Resolve failing OrganizationServiceTests.cs
* dotnet format
* PM-3275 - PoliciesController.cs - GetMasterPasswordPolicy bugfix - for orgs without a MP policy, policy comes back as null and we should return notFound in that case.
* PM-3275 - Add PoliciesControllerTests.cs specifically for new GetMasterPasswordPolicy(...) endpoint.
* PM-3275 - dotnet format PoliciesControllerTests.cs
* PM-3275 - PoliciesController.cs - (1) Add tech debt task number (2) Properly flag endpoint as deprecated
* PM-3275 - Add new hasManageResetPasswordPermission property to ProfileResponseModel.cs primarily for sync so that we can condition client side if TDE user obtains elevated permissions
* PM-3275 - Fix AccountsControllerTests.cs
* PM-3275 - OrgUserInviteTokenable.cs - clarify TODO
* PM-3275 - AcceptOrgUserCommand.cs - Refactor token validation to use short circuiting to only run old token validation if new token validation fails.
* PM-3275 - OrgUserInviteTokenable.cs - (1) Add new static methods to centralize validation logic to avoid repetition (2) Add new token validation method so we can avoid having to pass in a full org user (and hitting the db to do so)
* PM-3275 - Realized that the old token validation was used in the PoliciesController.cs (existing user clicks invite link in email and goes to log in) and UserService.cs (user clicks invite link in email and registers for a new acct). Added tech debt item for cleaning up backwards compatibility in future.
* dotnet format
* PM-3275 - (1) AccountsController.cs - Update PostSetPasswordAsync SetPasswordRequestModel to allow null keys for the case where we have a TDE user who obtains elevated permissions - they already have a user public and user encrypted private key saved in the db. (2) AccountsControllerTests.cs - test PostSetPasswordAsync scenarios to ensure changes will work as expected.
* PM-3275 - PR review feedback - (1) set CurrentContext to private (2) Refactor GetProfile to use variables to improve clarity and simplify debugging.
* PM-3275 - SyncController.cs - PR Review Feedback - Set current context as private instead of protected.
* PM-3275 - CurrentContextExtensions.cs - PR Feedback - move parenthesis up from own line.
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - Replace unnecessary variable
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - PR Feedback - Add expected outcome statement to test name
* PM-3275 - Set Initial Password command and tests - PR Feedback changes - (1) Rename orgIdentifier --> OrgSsoIdentifier for clarity (2) Update SetInitialMasterPasswordAsync to not allow null orgSsoId with explicit message saying this vs letting null org trigger invalid organization (3) Add test to cover this new scenario.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Move summary from implementation to interface to better respect standards and the fact that the interface is the more seen piece of code.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, rename AcceptOrgUserByTokenAsync -> AcceptOrgUserByEmailTokenAsync + replace generic name token with emailToken
* PM-3275 - OrganizationService.cs - Per PR feedback, remove dupe line
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove new lines in error messages for consistency.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Per PR feedback, adjust formatting of constructor for improved readability.
* PM-3275 - CurrentContextExtensions.cs - Refactor AnyOrgUserHasManageResetPasswordPermission per PR feedback to remove unnecessary var.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove completed TODO
* PM-3275 - PoliciesController.cs - Per PR feedback, update GetByInvitedUser param to be guid instead of string.
* PM-3275 - OrgUserInviteTokenable.cs - per PR feedback, add tech debt item info.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, use const purpose from tokenable instead of magic string.
* PM-3275 - Restore non duplicate line to fix tests
* PM-3275 - Per PR feedback, revert all sync controller changes as the ProfileResponseModel.organizations array has org objects which have permissions which have the ManageResetPassword permission. So, I have the information that I need clientside already to determine if the user has the ManageResetPassword in any org.
* PM-3275 - PoliciesControllerTests.cs - Update imports as the PoliciesController was moved under the admin console team's domain.
* PM-3275 - Resolve issues from merge conflict resolutions to get solution building.
* PM-3275 / PM-4633 - PoliciesController.cs - use orgUserId to look up user instead of orgId. Oops.
* Fix user service tests
* Resolve merge conflict
2023-11-02 11:02:25 -04:00
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces ;
2025-01-06 12:10:53 -05:00
using Bit.Core.Platform.Push ;
2021-11-09 16:37:32 +01:00
using Bit.Core.Repositories ;
2021-02-22 15:35:16 -06:00
using Bit.Core.Settings ;
2023-10-30 08:40:06 -05:00
using Bit.Core.Tokens ;
2023-04-18 14:05:17 +02:00
using Bit.Core.Tools.Enums ;
using Bit.Core.Tools.Models.Business ;
using Bit.Core.Tools.Services ;
2021-11-09 16:37:32 +01:00
using Bit.Core.Utilities ;
2023-03-02 13:23:38 -05:00
using Bit.Core.Vault.Repositories ;
2021-03-22 23:21:43 +01:00
using Fido2NetLib ;
using Fido2NetLib.Objects ;
2021-11-09 16:37:32 +01:00
using Microsoft.AspNetCore.DataProtection ;
using Microsoft.AspNetCore.Identity ;
2025-01-09 18:10:54 -08:00
using Microsoft.Extensions.Caching.Distributed ;
2021-11-09 16:37:32 +01:00
using Microsoft.Extensions.Logging ;
using Microsoft.Extensions.Options ;
using File = System . IO . File ;
2024-02-26 11:50:24 -05:00
using JsonSerializer = System . Text . Json . JsonSerializer ;
2015-12-08 22:57:38 -05:00
2018-08-28 16:23:58 -04:00
namespace Bit.Core.Services ;
2017-01-14 10:02:37 -05:00
2017-01-11 21:46:36 -05:00
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 ;
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 ;
2017-01-11 21:46:36 -05:00
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 ;
2023-05-12 08:22:19 +01:00
private readonly IPolicyService _policyService ;
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 ;
2017-01-11 21:46:36 -05:00
private readonly ICurrentContext _currentContext ;
private readonly IGlobalSettings _globalSettings ;
Auth/PM-3275 - Changes to support TDE User without MP being able to Set a Password + misc refactoring (#3242)
* PM-3275 - Add new GetMasterPasswordPolicy endpoint which will allow authenticated clients to get an enabled MP org policy if it exists for the purposes of enforcing those policy requirements when setting a password.
* PM-3275 - AccountsController.cs - PostSetPasswordAsync - (1) Convert UserService.setPasswordAsync into new SetInitialMasterPasswordCommand (2) Refactor SetInitialMasterPasswordCommand to only accept post SSO users who are in the invited state
(3) Add TODOs for more cleanup work and more commands
* PM-3275 - Update AccountsControllerTests.cs to add new SetInitialMasterPasswordCommand
* PM-3275 - UserService.cs - Remove non implemented ChangePasswordAsync method
* PM-3275 - The new SetInitialMasterPasswordCommand leveraged the OrganizationService.cs AcceptUserAsync method so while I was in here I converted the AcceptUserAsync methods into a new AcceptOrgUserCommand.cs and turned the private method which accepted an existing org user public for use in the SetInitialMasterPasswordCommand
* PM-3275 - Dotnet format
* PM-3275 - Test SetInitialMasterPasswordCommand
* Dotnet format
* PM-3275 - In process AcceptOrgUserCommandTests.cs
* PM-3275 - Migrate changes from AC-244 / #3199 over into new AcceptOrgUserCommand
* PM-3275 - AcceptOrgUserCommand.cs - create data protector specifically for this command
* PM-3275 - Add TODO for renaming / removing overloading of methods to improve readability / clarity
* PM-3275 - AcceptOrgUserCommand.cs - refactor AcceptOrgUserAsync by OrgId to retrieve orgUser with _organizationUserRepository.GetByOrganizationAsync which gets a single user instead of a collection
* PM-3275 - AcceptOrgUserCommand.cs - update name in TODO for evaluation later
* PM-3275 / PM-1196 - (1) Slightly refactor SsoEmail2faSessionTokenable to provide public static GetTokenLifeTime() method for testing (2) Add missed tests to SsoEmail2faSessionTokenable in preparation for building tests for new OrgUserInviteTokenable.cs
* PM-3275 / PM-1196 - Removing SsoEmail2faSessionTokenable.cs changes + tests as I've handled that separately in a new PR (#3270) for newly created task PM-3925
* PM-3275 - ExpiringTokenable.cs - add clarifying comments to help distinguish between the Valid property and the TokenIsValid method.
* PM-3275 - Create OrgUserInviteTokenable.cs and add tests in OrgUserInviteTokenableTests.cs
* PM-3275 - OrganizationService.cs - Refactor Org User Invite methods to use new OrgUserInviteTokenable instead of manual creation of a token
* PM-3275 - OrgUserInviteTokenable.cs - clarify backwards compat note
* PM-3275 - AcceptOrgUserCommand.cs - Add TODOs + minor name refactor
* PM-3275 - AcceptOrgUserCommand.cs - replace method overloading with more easily readable names.
* PM-3275 - AcceptOrgUserCommand.cs - Update ValidateOrgUserInviteToken to add new token validation while maintaining backwards compatibility for 1 release.
* dotnet format
* PM-3275 - AcceptOrgUserCommand.cs - Move private method below where it is used
* PM-3275 - ServiceCollectionExtensions.cs - Must register IDataProtectorTokenFactory<OrgUserInviteTokenable> for new tokenable
* PM-3275 - OrgUserInviteTokenable needed access to global settings to set its token lifetime to the _globalSettings.OrganizationInviteExpirationHours value. Creating a factory seemed the most straightforward way to encapsulate the desired creation logic. Unsure if in the correct location in ServiceCollectionExtensions.cs but will figure that out later.
* PM-3275 - In process work of creating AcceptOrgUserCommandTests.cs
* PM-3275 - Remove no longer relevant AcceptOrgUser tests from OrganizationServiceTests.cs
* PM-3275 - Register OrgUserInviteTokenableFactory alongside tokenizer
* PM-3275 - AcceptOrgUserCommandTests.cs - AcceptOrgUserAsync basic test suite completed.
* PM-3275 - AcceptOrgUserCommandTests.cs - tweak test names
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Remove old tests from OrganizationServiceTests as no longer needed to reference (2) Add summary for SetupCommonAcceptOrgUserMocks (3) Get AcceptOrgUserByToken_OldToken_AcceptsUserAndVerifiesEmail passing
* PM-3275 - Create interface for OrgUserInviteTokenableFactory b/c that's the right thing to do + enables test substitution
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Start work on AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail (2) Create and use SetupCommonAcceptOrgUserByTokenMocks() (3) Create generic FakeDataProtectorTokenFactory for tokenable testing
* PM-3275 - (1) Get AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail test passing (2) Move FakeDataProtectorTokenFactory to own file
* PM-3275 - AcceptOrgUserCommandTests.cs - Finish up tests for AcceptOrgUserByTokenAsync
* PM-3275 - Add pseudo section comments
* PM-3275 - Clean up unused params on AcceptOrgUserByToken_EmailMismatch_ThrowsBadRequest test
* PM-3275 - (1) Tests written for AcceptOrgUserByOrgSsoIdAsync (2) Refactor happy path assertions into helper function AssertValidAcceptedOrgUser to reduce code duplication
* PM-3275 - Finish up testing AcceptOrgUserCommandTests.cs by adding tests for AcceptOrgUserByOrgIdAsync
* PM-3275 - Tweaking test naming to ensure consistency.
* PM-3275 - Bugfix - OrgUserInviteTokenableFactory implementation required when declaring singleton service in ServiceCollectionExtensions.cs
* PM-3275 - Resolve failing OrganizationServiceTests.cs
* dotnet format
* PM-3275 - PoliciesController.cs - GetMasterPasswordPolicy bugfix - for orgs without a MP policy, policy comes back as null and we should return notFound in that case.
* PM-3275 - Add PoliciesControllerTests.cs specifically for new GetMasterPasswordPolicy(...) endpoint.
* PM-3275 - dotnet format PoliciesControllerTests.cs
* PM-3275 - PoliciesController.cs - (1) Add tech debt task number (2) Properly flag endpoint as deprecated
* PM-3275 - Add new hasManageResetPasswordPermission property to ProfileResponseModel.cs primarily for sync so that we can condition client side if TDE user obtains elevated permissions
* PM-3275 - Fix AccountsControllerTests.cs
* PM-3275 - OrgUserInviteTokenable.cs - clarify TODO
* PM-3275 - AcceptOrgUserCommand.cs - Refactor token validation to use short circuiting to only run old token validation if new token validation fails.
* PM-3275 - OrgUserInviteTokenable.cs - (1) Add new static methods to centralize validation logic to avoid repetition (2) Add new token validation method so we can avoid having to pass in a full org user (and hitting the db to do so)
* PM-3275 - Realized that the old token validation was used in the PoliciesController.cs (existing user clicks invite link in email and goes to log in) and UserService.cs (user clicks invite link in email and registers for a new acct). Added tech debt item for cleaning up backwards compatibility in future.
* dotnet format
* PM-3275 - (1) AccountsController.cs - Update PostSetPasswordAsync SetPasswordRequestModel to allow null keys for the case where we have a TDE user who obtains elevated permissions - they already have a user public and user encrypted private key saved in the db. (2) AccountsControllerTests.cs - test PostSetPasswordAsync scenarios to ensure changes will work as expected.
* PM-3275 - PR review feedback - (1) set CurrentContext to private (2) Refactor GetProfile to use variables to improve clarity and simplify debugging.
* PM-3275 - SyncController.cs - PR Review Feedback - Set current context as private instead of protected.
* PM-3275 - CurrentContextExtensions.cs - PR Feedback - move parenthesis up from own line.
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - Replace unnecessary variable
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - PR Feedback - Add expected outcome statement to test name
* PM-3275 - Set Initial Password command and tests - PR Feedback changes - (1) Rename orgIdentifier --> OrgSsoIdentifier for clarity (2) Update SetInitialMasterPasswordAsync to not allow null orgSsoId with explicit message saying this vs letting null org trigger invalid organization (3) Add test to cover this new scenario.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Move summary from implementation to interface to better respect standards and the fact that the interface is the more seen piece of code.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, rename AcceptOrgUserByTokenAsync -> AcceptOrgUserByEmailTokenAsync + replace generic name token with emailToken
* PM-3275 - OrganizationService.cs - Per PR feedback, remove dupe line
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove new lines in error messages for consistency.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Per PR feedback, adjust formatting of constructor for improved readability.
* PM-3275 - CurrentContextExtensions.cs - Refactor AnyOrgUserHasManageResetPasswordPermission per PR feedback to remove unnecessary var.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove completed TODO
* PM-3275 - PoliciesController.cs - Per PR feedback, update GetByInvitedUser param to be guid instead of string.
* PM-3275 - OrgUserInviteTokenable.cs - per PR feedback, add tech debt item info.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, use const purpose from tokenable instead of magic string.
* PM-3275 - Restore non duplicate line to fix tests
* PM-3275 - Per PR feedback, revert all sync controller changes as the ProfileResponseModel.organizations array has org objects which have permissions which have the ManageResetPassword permission. So, I have the information that I need clientside already to determine if the user has the ManageResetPassword in any org.
* PM-3275 - PoliciesControllerTests.cs - Update imports as the PoliciesController was moved under the admin console team's domain.
* PM-3275 - Resolve issues from merge conflict resolutions to get solution building.
* PM-3275 / PM-4633 - PoliciesController.cs - use orgUserId to look up user instead of orgId. Oops.
* Fix user service tests
* Resolve merge conflict
2023-11-02 11:02:25 -04:00
private readonly IAcceptOrgUserCommand _acceptOrgUserCommand ;
2021-09-15 20:34:06 +02:00
private readonly IProviderUserRepository _providerUserRepository ;
2022-06-16 17:45:26 +01:00
private readonly IStripeSyncService _stripeSyncService ;
Auth/PM-3275 - Changes to support TDE User without MP being able to Set a Password + misc refactoring (#3242)
* PM-3275 - Add new GetMasterPasswordPolicy endpoint which will allow authenticated clients to get an enabled MP org policy if it exists for the purposes of enforcing those policy requirements when setting a password.
* PM-3275 - AccountsController.cs - PostSetPasswordAsync - (1) Convert UserService.setPasswordAsync into new SetInitialMasterPasswordCommand (2) Refactor SetInitialMasterPasswordCommand to only accept post SSO users who are in the invited state
(3) Add TODOs for more cleanup work and more commands
* PM-3275 - Update AccountsControllerTests.cs to add new SetInitialMasterPasswordCommand
* PM-3275 - UserService.cs - Remove non implemented ChangePasswordAsync method
* PM-3275 - The new SetInitialMasterPasswordCommand leveraged the OrganizationService.cs AcceptUserAsync method so while I was in here I converted the AcceptUserAsync methods into a new AcceptOrgUserCommand.cs and turned the private method which accepted an existing org user public for use in the SetInitialMasterPasswordCommand
* PM-3275 - Dotnet format
* PM-3275 - Test SetInitialMasterPasswordCommand
* Dotnet format
* PM-3275 - In process AcceptOrgUserCommandTests.cs
* PM-3275 - Migrate changes from AC-244 / #3199 over into new AcceptOrgUserCommand
* PM-3275 - AcceptOrgUserCommand.cs - create data protector specifically for this command
* PM-3275 - Add TODO for renaming / removing overloading of methods to improve readability / clarity
* PM-3275 - AcceptOrgUserCommand.cs - refactor AcceptOrgUserAsync by OrgId to retrieve orgUser with _organizationUserRepository.GetByOrganizationAsync which gets a single user instead of a collection
* PM-3275 - AcceptOrgUserCommand.cs - update name in TODO for evaluation later
* PM-3275 / PM-1196 - (1) Slightly refactor SsoEmail2faSessionTokenable to provide public static GetTokenLifeTime() method for testing (2) Add missed tests to SsoEmail2faSessionTokenable in preparation for building tests for new OrgUserInviteTokenable.cs
* PM-3275 / PM-1196 - Removing SsoEmail2faSessionTokenable.cs changes + tests as I've handled that separately in a new PR (#3270) for newly created task PM-3925
* PM-3275 - ExpiringTokenable.cs - add clarifying comments to help distinguish between the Valid property and the TokenIsValid method.
* PM-3275 - Create OrgUserInviteTokenable.cs and add tests in OrgUserInviteTokenableTests.cs
* PM-3275 - OrganizationService.cs - Refactor Org User Invite methods to use new OrgUserInviteTokenable instead of manual creation of a token
* PM-3275 - OrgUserInviteTokenable.cs - clarify backwards compat note
* PM-3275 - AcceptOrgUserCommand.cs - Add TODOs + minor name refactor
* PM-3275 - AcceptOrgUserCommand.cs - replace method overloading with more easily readable names.
* PM-3275 - AcceptOrgUserCommand.cs - Update ValidateOrgUserInviteToken to add new token validation while maintaining backwards compatibility for 1 release.
* dotnet format
* PM-3275 - AcceptOrgUserCommand.cs - Move private method below where it is used
* PM-3275 - ServiceCollectionExtensions.cs - Must register IDataProtectorTokenFactory<OrgUserInviteTokenable> for new tokenable
* PM-3275 - OrgUserInviteTokenable needed access to global settings to set its token lifetime to the _globalSettings.OrganizationInviteExpirationHours value. Creating a factory seemed the most straightforward way to encapsulate the desired creation logic. Unsure if in the correct location in ServiceCollectionExtensions.cs but will figure that out later.
* PM-3275 - In process work of creating AcceptOrgUserCommandTests.cs
* PM-3275 - Remove no longer relevant AcceptOrgUser tests from OrganizationServiceTests.cs
* PM-3275 - Register OrgUserInviteTokenableFactory alongside tokenizer
* PM-3275 - AcceptOrgUserCommandTests.cs - AcceptOrgUserAsync basic test suite completed.
* PM-3275 - AcceptOrgUserCommandTests.cs - tweak test names
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Remove old tests from OrganizationServiceTests as no longer needed to reference (2) Add summary for SetupCommonAcceptOrgUserMocks (3) Get AcceptOrgUserByToken_OldToken_AcceptsUserAndVerifiesEmail passing
* PM-3275 - Create interface for OrgUserInviteTokenableFactory b/c that's the right thing to do + enables test substitution
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Start work on AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail (2) Create and use SetupCommonAcceptOrgUserByTokenMocks() (3) Create generic FakeDataProtectorTokenFactory for tokenable testing
* PM-3275 - (1) Get AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail test passing (2) Move FakeDataProtectorTokenFactory to own file
* PM-3275 - AcceptOrgUserCommandTests.cs - Finish up tests for AcceptOrgUserByTokenAsync
* PM-3275 - Add pseudo section comments
* PM-3275 - Clean up unused params on AcceptOrgUserByToken_EmailMismatch_ThrowsBadRequest test
* PM-3275 - (1) Tests written for AcceptOrgUserByOrgSsoIdAsync (2) Refactor happy path assertions into helper function AssertValidAcceptedOrgUser to reduce code duplication
* PM-3275 - Finish up testing AcceptOrgUserCommandTests.cs by adding tests for AcceptOrgUserByOrgIdAsync
* PM-3275 - Tweaking test naming to ensure consistency.
* PM-3275 - Bugfix - OrgUserInviteTokenableFactory implementation required when declaring singleton service in ServiceCollectionExtensions.cs
* PM-3275 - Resolve failing OrganizationServiceTests.cs
* dotnet format
* PM-3275 - PoliciesController.cs - GetMasterPasswordPolicy bugfix - for orgs without a MP policy, policy comes back as null and we should return notFound in that case.
* PM-3275 - Add PoliciesControllerTests.cs specifically for new GetMasterPasswordPolicy(...) endpoint.
* PM-3275 - dotnet format PoliciesControllerTests.cs
* PM-3275 - PoliciesController.cs - (1) Add tech debt task number (2) Properly flag endpoint as deprecated
* PM-3275 - Add new hasManageResetPasswordPermission property to ProfileResponseModel.cs primarily for sync so that we can condition client side if TDE user obtains elevated permissions
* PM-3275 - Fix AccountsControllerTests.cs
* PM-3275 - OrgUserInviteTokenable.cs - clarify TODO
* PM-3275 - AcceptOrgUserCommand.cs - Refactor token validation to use short circuiting to only run old token validation if new token validation fails.
* PM-3275 - OrgUserInviteTokenable.cs - (1) Add new static methods to centralize validation logic to avoid repetition (2) Add new token validation method so we can avoid having to pass in a full org user (and hitting the db to do so)
* PM-3275 - Realized that the old token validation was used in the PoliciesController.cs (existing user clicks invite link in email and goes to log in) and UserService.cs (user clicks invite link in email and registers for a new acct). Added tech debt item for cleaning up backwards compatibility in future.
* dotnet format
* PM-3275 - (1) AccountsController.cs - Update PostSetPasswordAsync SetPasswordRequestModel to allow null keys for the case where we have a TDE user who obtains elevated permissions - they already have a user public and user encrypted private key saved in the db. (2) AccountsControllerTests.cs - test PostSetPasswordAsync scenarios to ensure changes will work as expected.
* PM-3275 - PR review feedback - (1) set CurrentContext to private (2) Refactor GetProfile to use variables to improve clarity and simplify debugging.
* PM-3275 - SyncController.cs - PR Review Feedback - Set current context as private instead of protected.
* PM-3275 - CurrentContextExtensions.cs - PR Feedback - move parenthesis up from own line.
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - Replace unnecessary variable
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - PR Feedback - Add expected outcome statement to test name
* PM-3275 - Set Initial Password command and tests - PR Feedback changes - (1) Rename orgIdentifier --> OrgSsoIdentifier for clarity (2) Update SetInitialMasterPasswordAsync to not allow null orgSsoId with explicit message saying this vs letting null org trigger invalid organization (3) Add test to cover this new scenario.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Move summary from implementation to interface to better respect standards and the fact that the interface is the more seen piece of code.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, rename AcceptOrgUserByTokenAsync -> AcceptOrgUserByEmailTokenAsync + replace generic name token with emailToken
* PM-3275 - OrganizationService.cs - Per PR feedback, remove dupe line
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove new lines in error messages for consistency.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Per PR feedback, adjust formatting of constructor for improved readability.
* PM-3275 - CurrentContextExtensions.cs - Refactor AnyOrgUserHasManageResetPasswordPermission per PR feedback to remove unnecessary var.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove completed TODO
* PM-3275 - PoliciesController.cs - Per PR feedback, update GetByInvitedUser param to be guid instead of string.
* PM-3275 - OrgUserInviteTokenable.cs - per PR feedback, add tech debt item info.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, use const purpose from tokenable instead of magic string.
* PM-3275 - Restore non duplicate line to fix tests
* PM-3275 - Per PR feedback, revert all sync controller changes as the ProfileResponseModel.organizations array has org objects which have permissions which have the ManageResetPassword permission. So, I have the information that I need clientside already to determine if the user has the ManageResetPassword in any org.
* PM-3275 - PoliciesControllerTests.cs - Update imports as the PoliciesController was moved under the admin console team's domain.
* PM-3275 - Resolve issues from merge conflict resolutions to get solution building.
* PM-3275 / PM-4633 - PoliciesController.cs - use orgUserId to look up user instead of orgId. Oops.
* Fix user service tests
* Resolve merge conflict
2023-11-02 11:02:25 -04:00
private readonly IDataProtectorTokenFactory < OrgUserInviteTokenable > _orgUserInviteTokenDataFactory ;
2024-10-01 09:12:08 -04:00
private readonly IFeatureService _featureService ;
private readonly IPremiumUserBillingService _premiumUserBillingService ;
2024-10-16 10:33:00 +01:00
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand ;
2024-12-13 11:32:29 +00:00
private readonly IRevokeNonCompliantOrganizationUserCommand _revokeNonCompliantOrganizationUserCommand ;
2025-01-09 18:10:54 -08:00
private readonly IDistributedCache _distributedCache ;
2022-08-29 16:06:55 -04:00
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 ,
2023-05-12 08:22:19 +01:00
IPolicyService policyService ,
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 ,
2022-05-13 10:48:48 -03:00
IGlobalSettings globalSettings ,
Auth/PM-3275 - Changes to support TDE User without MP being able to Set a Password + misc refactoring (#3242)
* PM-3275 - Add new GetMasterPasswordPolicy endpoint which will allow authenticated clients to get an enabled MP org policy if it exists for the purposes of enforcing those policy requirements when setting a password.
* PM-3275 - AccountsController.cs - PostSetPasswordAsync - (1) Convert UserService.setPasswordAsync into new SetInitialMasterPasswordCommand (2) Refactor SetInitialMasterPasswordCommand to only accept post SSO users who are in the invited state
(3) Add TODOs for more cleanup work and more commands
* PM-3275 - Update AccountsControllerTests.cs to add new SetInitialMasterPasswordCommand
* PM-3275 - UserService.cs - Remove non implemented ChangePasswordAsync method
* PM-3275 - The new SetInitialMasterPasswordCommand leveraged the OrganizationService.cs AcceptUserAsync method so while I was in here I converted the AcceptUserAsync methods into a new AcceptOrgUserCommand.cs and turned the private method which accepted an existing org user public for use in the SetInitialMasterPasswordCommand
* PM-3275 - Dotnet format
* PM-3275 - Test SetInitialMasterPasswordCommand
* Dotnet format
* PM-3275 - In process AcceptOrgUserCommandTests.cs
* PM-3275 - Migrate changes from AC-244 / #3199 over into new AcceptOrgUserCommand
* PM-3275 - AcceptOrgUserCommand.cs - create data protector specifically for this command
* PM-3275 - Add TODO for renaming / removing overloading of methods to improve readability / clarity
* PM-3275 - AcceptOrgUserCommand.cs - refactor AcceptOrgUserAsync by OrgId to retrieve orgUser with _organizationUserRepository.GetByOrganizationAsync which gets a single user instead of a collection
* PM-3275 - AcceptOrgUserCommand.cs - update name in TODO for evaluation later
* PM-3275 / PM-1196 - (1) Slightly refactor SsoEmail2faSessionTokenable to provide public static GetTokenLifeTime() method for testing (2) Add missed tests to SsoEmail2faSessionTokenable in preparation for building tests for new OrgUserInviteTokenable.cs
* PM-3275 / PM-1196 - Removing SsoEmail2faSessionTokenable.cs changes + tests as I've handled that separately in a new PR (#3270) for newly created task PM-3925
* PM-3275 - ExpiringTokenable.cs - add clarifying comments to help distinguish between the Valid property and the TokenIsValid method.
* PM-3275 - Create OrgUserInviteTokenable.cs and add tests in OrgUserInviteTokenableTests.cs
* PM-3275 - OrganizationService.cs - Refactor Org User Invite methods to use new OrgUserInviteTokenable instead of manual creation of a token
* PM-3275 - OrgUserInviteTokenable.cs - clarify backwards compat note
* PM-3275 - AcceptOrgUserCommand.cs - Add TODOs + minor name refactor
* PM-3275 - AcceptOrgUserCommand.cs - replace method overloading with more easily readable names.
* PM-3275 - AcceptOrgUserCommand.cs - Update ValidateOrgUserInviteToken to add new token validation while maintaining backwards compatibility for 1 release.
* dotnet format
* PM-3275 - AcceptOrgUserCommand.cs - Move private method below where it is used
* PM-3275 - ServiceCollectionExtensions.cs - Must register IDataProtectorTokenFactory<OrgUserInviteTokenable> for new tokenable
* PM-3275 - OrgUserInviteTokenable needed access to global settings to set its token lifetime to the _globalSettings.OrganizationInviteExpirationHours value. Creating a factory seemed the most straightforward way to encapsulate the desired creation logic. Unsure if in the correct location in ServiceCollectionExtensions.cs but will figure that out later.
* PM-3275 - In process work of creating AcceptOrgUserCommandTests.cs
* PM-3275 - Remove no longer relevant AcceptOrgUser tests from OrganizationServiceTests.cs
* PM-3275 - Register OrgUserInviteTokenableFactory alongside tokenizer
* PM-3275 - AcceptOrgUserCommandTests.cs - AcceptOrgUserAsync basic test suite completed.
* PM-3275 - AcceptOrgUserCommandTests.cs - tweak test names
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Remove old tests from OrganizationServiceTests as no longer needed to reference (2) Add summary for SetupCommonAcceptOrgUserMocks (3) Get AcceptOrgUserByToken_OldToken_AcceptsUserAndVerifiesEmail passing
* PM-3275 - Create interface for OrgUserInviteTokenableFactory b/c that's the right thing to do + enables test substitution
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Start work on AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail (2) Create and use SetupCommonAcceptOrgUserByTokenMocks() (3) Create generic FakeDataProtectorTokenFactory for tokenable testing
* PM-3275 - (1) Get AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail test passing (2) Move FakeDataProtectorTokenFactory to own file
* PM-3275 - AcceptOrgUserCommandTests.cs - Finish up tests for AcceptOrgUserByTokenAsync
* PM-3275 - Add pseudo section comments
* PM-3275 - Clean up unused params on AcceptOrgUserByToken_EmailMismatch_ThrowsBadRequest test
* PM-3275 - (1) Tests written for AcceptOrgUserByOrgSsoIdAsync (2) Refactor happy path assertions into helper function AssertValidAcceptedOrgUser to reduce code duplication
* PM-3275 - Finish up testing AcceptOrgUserCommandTests.cs by adding tests for AcceptOrgUserByOrgIdAsync
* PM-3275 - Tweaking test naming to ensure consistency.
* PM-3275 - Bugfix - OrgUserInviteTokenableFactory implementation required when declaring singleton service in ServiceCollectionExtensions.cs
* PM-3275 - Resolve failing OrganizationServiceTests.cs
* dotnet format
* PM-3275 - PoliciesController.cs - GetMasterPasswordPolicy bugfix - for orgs without a MP policy, policy comes back as null and we should return notFound in that case.
* PM-3275 - Add PoliciesControllerTests.cs specifically for new GetMasterPasswordPolicy(...) endpoint.
* PM-3275 - dotnet format PoliciesControllerTests.cs
* PM-3275 - PoliciesController.cs - (1) Add tech debt task number (2) Properly flag endpoint as deprecated
* PM-3275 - Add new hasManageResetPasswordPermission property to ProfileResponseModel.cs primarily for sync so that we can condition client side if TDE user obtains elevated permissions
* PM-3275 - Fix AccountsControllerTests.cs
* PM-3275 - OrgUserInviteTokenable.cs - clarify TODO
* PM-3275 - AcceptOrgUserCommand.cs - Refactor token validation to use short circuiting to only run old token validation if new token validation fails.
* PM-3275 - OrgUserInviteTokenable.cs - (1) Add new static methods to centralize validation logic to avoid repetition (2) Add new token validation method so we can avoid having to pass in a full org user (and hitting the db to do so)
* PM-3275 - Realized that the old token validation was used in the PoliciesController.cs (existing user clicks invite link in email and goes to log in) and UserService.cs (user clicks invite link in email and registers for a new acct). Added tech debt item for cleaning up backwards compatibility in future.
* dotnet format
* PM-3275 - (1) AccountsController.cs - Update PostSetPasswordAsync SetPasswordRequestModel to allow null keys for the case where we have a TDE user who obtains elevated permissions - they already have a user public and user encrypted private key saved in the db. (2) AccountsControllerTests.cs - test PostSetPasswordAsync scenarios to ensure changes will work as expected.
* PM-3275 - PR review feedback - (1) set CurrentContext to private (2) Refactor GetProfile to use variables to improve clarity and simplify debugging.
* PM-3275 - SyncController.cs - PR Review Feedback - Set current context as private instead of protected.
* PM-3275 - CurrentContextExtensions.cs - PR Feedback - move parenthesis up from own line.
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - Replace unnecessary variable
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - PR Feedback - Add expected outcome statement to test name
* PM-3275 - Set Initial Password command and tests - PR Feedback changes - (1) Rename orgIdentifier --> OrgSsoIdentifier for clarity (2) Update SetInitialMasterPasswordAsync to not allow null orgSsoId with explicit message saying this vs letting null org trigger invalid organization (3) Add test to cover this new scenario.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Move summary from implementation to interface to better respect standards and the fact that the interface is the more seen piece of code.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, rename AcceptOrgUserByTokenAsync -> AcceptOrgUserByEmailTokenAsync + replace generic name token with emailToken
* PM-3275 - OrganizationService.cs - Per PR feedback, remove dupe line
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove new lines in error messages for consistency.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Per PR feedback, adjust formatting of constructor for improved readability.
* PM-3275 - CurrentContextExtensions.cs - Refactor AnyOrgUserHasManageResetPasswordPermission per PR feedback to remove unnecessary var.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove completed TODO
* PM-3275 - PoliciesController.cs - Per PR feedback, update GetByInvitedUser param to be guid instead of string.
* PM-3275 - OrgUserInviteTokenable.cs - per PR feedback, add tech debt item info.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, use const purpose from tokenable instead of magic string.
* PM-3275 - Restore non duplicate line to fix tests
* PM-3275 - Per PR feedback, revert all sync controller changes as the ProfileResponseModel.organizations array has org objects which have permissions which have the ManageResetPassword permission. So, I have the information that I need clientside already to determine if the user has the ManageResetPassword in any org.
* PM-3275 - PoliciesControllerTests.cs - Update imports as the PoliciesController was moved under the admin console team's domain.
* PM-3275 - Resolve issues from merge conflict resolutions to get solution building.
* PM-3275 / PM-4633 - PoliciesController.cs - use orgUserId to look up user instead of orgId. Oops.
* Fix user service tests
* Resolve merge conflict
2023-11-02 11:02:25 -04:00
IAcceptOrgUserCommand acceptOrgUserCommand ,
2022-04-28 13:14:09 -03:00
IProviderUserRepository providerUserRepository ,
2023-10-30 08:40:06 -05:00
IStripeSyncService stripeSyncService ,
2024-10-01 09:12:08 -04:00
IDataProtectorTokenFactory < OrgUserInviteTokenable > orgUserInviteTokenDataFactory ,
IFeatureService featureService ,
2024-10-16 10:33:00 +01:00
IPremiumUserBillingService premiumUserBillingService ,
2024-12-13 11:32:29 +00:00
IRemoveOrganizationUserCommand removeOrganizationUserCommand ,
2025-01-09 18:10:54 -08:00
IRevokeNonCompliantOrganizationUserCommand revokeNonCompliantOrganizationUserCommand ,
IDistributedCache distributedCache )
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 ;
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 ;
2023-05-12 08:22:19 +01:00
_policyService = policyService ;
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 ;
Auth/PM-3275 - Changes to support TDE User without MP being able to Set a Password + misc refactoring (#3242)
* PM-3275 - Add new GetMasterPasswordPolicy endpoint which will allow authenticated clients to get an enabled MP org policy if it exists for the purposes of enforcing those policy requirements when setting a password.
* PM-3275 - AccountsController.cs - PostSetPasswordAsync - (1) Convert UserService.setPasswordAsync into new SetInitialMasterPasswordCommand (2) Refactor SetInitialMasterPasswordCommand to only accept post SSO users who are in the invited state
(3) Add TODOs for more cleanup work and more commands
* PM-3275 - Update AccountsControllerTests.cs to add new SetInitialMasterPasswordCommand
* PM-3275 - UserService.cs - Remove non implemented ChangePasswordAsync method
* PM-3275 - The new SetInitialMasterPasswordCommand leveraged the OrganizationService.cs AcceptUserAsync method so while I was in here I converted the AcceptUserAsync methods into a new AcceptOrgUserCommand.cs and turned the private method which accepted an existing org user public for use in the SetInitialMasterPasswordCommand
* PM-3275 - Dotnet format
* PM-3275 - Test SetInitialMasterPasswordCommand
* Dotnet format
* PM-3275 - In process AcceptOrgUserCommandTests.cs
* PM-3275 - Migrate changes from AC-244 / #3199 over into new AcceptOrgUserCommand
* PM-3275 - AcceptOrgUserCommand.cs - create data protector specifically for this command
* PM-3275 - Add TODO for renaming / removing overloading of methods to improve readability / clarity
* PM-3275 - AcceptOrgUserCommand.cs - refactor AcceptOrgUserAsync by OrgId to retrieve orgUser with _organizationUserRepository.GetByOrganizationAsync which gets a single user instead of a collection
* PM-3275 - AcceptOrgUserCommand.cs - update name in TODO for evaluation later
* PM-3275 / PM-1196 - (1) Slightly refactor SsoEmail2faSessionTokenable to provide public static GetTokenLifeTime() method for testing (2) Add missed tests to SsoEmail2faSessionTokenable in preparation for building tests for new OrgUserInviteTokenable.cs
* PM-3275 / PM-1196 - Removing SsoEmail2faSessionTokenable.cs changes + tests as I've handled that separately in a new PR (#3270) for newly created task PM-3925
* PM-3275 - ExpiringTokenable.cs - add clarifying comments to help distinguish between the Valid property and the TokenIsValid method.
* PM-3275 - Create OrgUserInviteTokenable.cs and add tests in OrgUserInviteTokenableTests.cs
* PM-3275 - OrganizationService.cs - Refactor Org User Invite methods to use new OrgUserInviteTokenable instead of manual creation of a token
* PM-3275 - OrgUserInviteTokenable.cs - clarify backwards compat note
* PM-3275 - AcceptOrgUserCommand.cs - Add TODOs + minor name refactor
* PM-3275 - AcceptOrgUserCommand.cs - replace method overloading with more easily readable names.
* PM-3275 - AcceptOrgUserCommand.cs - Update ValidateOrgUserInviteToken to add new token validation while maintaining backwards compatibility for 1 release.
* dotnet format
* PM-3275 - AcceptOrgUserCommand.cs - Move private method below where it is used
* PM-3275 - ServiceCollectionExtensions.cs - Must register IDataProtectorTokenFactory<OrgUserInviteTokenable> for new tokenable
* PM-3275 - OrgUserInviteTokenable needed access to global settings to set its token lifetime to the _globalSettings.OrganizationInviteExpirationHours value. Creating a factory seemed the most straightforward way to encapsulate the desired creation logic. Unsure if in the correct location in ServiceCollectionExtensions.cs but will figure that out later.
* PM-3275 - In process work of creating AcceptOrgUserCommandTests.cs
* PM-3275 - Remove no longer relevant AcceptOrgUser tests from OrganizationServiceTests.cs
* PM-3275 - Register OrgUserInviteTokenableFactory alongside tokenizer
* PM-3275 - AcceptOrgUserCommandTests.cs - AcceptOrgUserAsync basic test suite completed.
* PM-3275 - AcceptOrgUserCommandTests.cs - tweak test names
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Remove old tests from OrganizationServiceTests as no longer needed to reference (2) Add summary for SetupCommonAcceptOrgUserMocks (3) Get AcceptOrgUserByToken_OldToken_AcceptsUserAndVerifiesEmail passing
* PM-3275 - Create interface for OrgUserInviteTokenableFactory b/c that's the right thing to do + enables test substitution
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Start work on AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail (2) Create and use SetupCommonAcceptOrgUserByTokenMocks() (3) Create generic FakeDataProtectorTokenFactory for tokenable testing
* PM-3275 - (1) Get AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail test passing (2) Move FakeDataProtectorTokenFactory to own file
* PM-3275 - AcceptOrgUserCommandTests.cs - Finish up tests for AcceptOrgUserByTokenAsync
* PM-3275 - Add pseudo section comments
* PM-3275 - Clean up unused params on AcceptOrgUserByToken_EmailMismatch_ThrowsBadRequest test
* PM-3275 - (1) Tests written for AcceptOrgUserByOrgSsoIdAsync (2) Refactor happy path assertions into helper function AssertValidAcceptedOrgUser to reduce code duplication
* PM-3275 - Finish up testing AcceptOrgUserCommandTests.cs by adding tests for AcceptOrgUserByOrgIdAsync
* PM-3275 - Tweaking test naming to ensure consistency.
* PM-3275 - Bugfix - OrgUserInviteTokenableFactory implementation required when declaring singleton service in ServiceCollectionExtensions.cs
* PM-3275 - Resolve failing OrganizationServiceTests.cs
* dotnet format
* PM-3275 - PoliciesController.cs - GetMasterPasswordPolicy bugfix - for orgs without a MP policy, policy comes back as null and we should return notFound in that case.
* PM-3275 - Add PoliciesControllerTests.cs specifically for new GetMasterPasswordPolicy(...) endpoint.
* PM-3275 - dotnet format PoliciesControllerTests.cs
* PM-3275 - PoliciesController.cs - (1) Add tech debt task number (2) Properly flag endpoint as deprecated
* PM-3275 - Add new hasManageResetPasswordPermission property to ProfileResponseModel.cs primarily for sync so that we can condition client side if TDE user obtains elevated permissions
* PM-3275 - Fix AccountsControllerTests.cs
* PM-3275 - OrgUserInviteTokenable.cs - clarify TODO
* PM-3275 - AcceptOrgUserCommand.cs - Refactor token validation to use short circuiting to only run old token validation if new token validation fails.
* PM-3275 - OrgUserInviteTokenable.cs - (1) Add new static methods to centralize validation logic to avoid repetition (2) Add new token validation method so we can avoid having to pass in a full org user (and hitting the db to do so)
* PM-3275 - Realized that the old token validation was used in the PoliciesController.cs (existing user clicks invite link in email and goes to log in) and UserService.cs (user clicks invite link in email and registers for a new acct). Added tech debt item for cleaning up backwards compatibility in future.
* dotnet format
* PM-3275 - (1) AccountsController.cs - Update PostSetPasswordAsync SetPasswordRequestModel to allow null keys for the case where we have a TDE user who obtains elevated permissions - they already have a user public and user encrypted private key saved in the db. (2) AccountsControllerTests.cs - test PostSetPasswordAsync scenarios to ensure changes will work as expected.
* PM-3275 - PR review feedback - (1) set CurrentContext to private (2) Refactor GetProfile to use variables to improve clarity and simplify debugging.
* PM-3275 - SyncController.cs - PR Review Feedback - Set current context as private instead of protected.
* PM-3275 - CurrentContextExtensions.cs - PR Feedback - move parenthesis up from own line.
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - Replace unnecessary variable
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - PR Feedback - Add expected outcome statement to test name
* PM-3275 - Set Initial Password command and tests - PR Feedback changes - (1) Rename orgIdentifier --> OrgSsoIdentifier for clarity (2) Update SetInitialMasterPasswordAsync to not allow null orgSsoId with explicit message saying this vs letting null org trigger invalid organization (3) Add test to cover this new scenario.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Move summary from implementation to interface to better respect standards and the fact that the interface is the more seen piece of code.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, rename AcceptOrgUserByTokenAsync -> AcceptOrgUserByEmailTokenAsync + replace generic name token with emailToken
* PM-3275 - OrganizationService.cs - Per PR feedback, remove dupe line
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove new lines in error messages for consistency.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Per PR feedback, adjust formatting of constructor for improved readability.
* PM-3275 - CurrentContextExtensions.cs - Refactor AnyOrgUserHasManageResetPasswordPermission per PR feedback to remove unnecessary var.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove completed TODO
* PM-3275 - PoliciesController.cs - Per PR feedback, update GetByInvitedUser param to be guid instead of string.
* PM-3275 - OrgUserInviteTokenable.cs - per PR feedback, add tech debt item info.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, use const purpose from tokenable instead of magic string.
* PM-3275 - Restore non duplicate line to fix tests
* PM-3275 - Per PR feedback, revert all sync controller changes as the ProfileResponseModel.organizations array has org objects which have permissions which have the ManageResetPassword permission. So, I have the information that I need clientside already to determine if the user has the ManageResetPassword in any org.
* PM-3275 - PoliciesControllerTests.cs - Update imports as the PoliciesController was moved under the admin console team's domain.
* PM-3275 - Resolve issues from merge conflict resolutions to get solution building.
* PM-3275 / PM-4633 - PoliciesController.cs - use orgUserId to look up user instead of orgId. Oops.
* Fix user service tests
* Resolve merge conflict
2023-11-02 11:02:25 -04:00
_acceptOrgUserCommand = acceptOrgUserCommand ;
2021-09-15 20:34:06 +02:00
_providerUserRepository = providerUserRepository ;
2022-06-16 17:45:26 +01:00
_stripeSyncService = stripeSyncService ;
Auth/PM-3275 - Changes to support TDE User without MP being able to Set a Password + misc refactoring (#3242)
* PM-3275 - Add new GetMasterPasswordPolicy endpoint which will allow authenticated clients to get an enabled MP org policy if it exists for the purposes of enforcing those policy requirements when setting a password.
* PM-3275 - AccountsController.cs - PostSetPasswordAsync - (1) Convert UserService.setPasswordAsync into new SetInitialMasterPasswordCommand (2) Refactor SetInitialMasterPasswordCommand to only accept post SSO users who are in the invited state
(3) Add TODOs for more cleanup work and more commands
* PM-3275 - Update AccountsControllerTests.cs to add new SetInitialMasterPasswordCommand
* PM-3275 - UserService.cs - Remove non implemented ChangePasswordAsync method
* PM-3275 - The new SetInitialMasterPasswordCommand leveraged the OrganizationService.cs AcceptUserAsync method so while I was in here I converted the AcceptUserAsync methods into a new AcceptOrgUserCommand.cs and turned the private method which accepted an existing org user public for use in the SetInitialMasterPasswordCommand
* PM-3275 - Dotnet format
* PM-3275 - Test SetInitialMasterPasswordCommand
* Dotnet format
* PM-3275 - In process AcceptOrgUserCommandTests.cs
* PM-3275 - Migrate changes from AC-244 / #3199 over into new AcceptOrgUserCommand
* PM-3275 - AcceptOrgUserCommand.cs - create data protector specifically for this command
* PM-3275 - Add TODO for renaming / removing overloading of methods to improve readability / clarity
* PM-3275 - AcceptOrgUserCommand.cs - refactor AcceptOrgUserAsync by OrgId to retrieve orgUser with _organizationUserRepository.GetByOrganizationAsync which gets a single user instead of a collection
* PM-3275 - AcceptOrgUserCommand.cs - update name in TODO for evaluation later
* PM-3275 / PM-1196 - (1) Slightly refactor SsoEmail2faSessionTokenable to provide public static GetTokenLifeTime() method for testing (2) Add missed tests to SsoEmail2faSessionTokenable in preparation for building tests for new OrgUserInviteTokenable.cs
* PM-3275 / PM-1196 - Removing SsoEmail2faSessionTokenable.cs changes + tests as I've handled that separately in a new PR (#3270) for newly created task PM-3925
* PM-3275 - ExpiringTokenable.cs - add clarifying comments to help distinguish between the Valid property and the TokenIsValid method.
* PM-3275 - Create OrgUserInviteTokenable.cs and add tests in OrgUserInviteTokenableTests.cs
* PM-3275 - OrganizationService.cs - Refactor Org User Invite methods to use new OrgUserInviteTokenable instead of manual creation of a token
* PM-3275 - OrgUserInviteTokenable.cs - clarify backwards compat note
* PM-3275 - AcceptOrgUserCommand.cs - Add TODOs + minor name refactor
* PM-3275 - AcceptOrgUserCommand.cs - replace method overloading with more easily readable names.
* PM-3275 - AcceptOrgUserCommand.cs - Update ValidateOrgUserInviteToken to add new token validation while maintaining backwards compatibility for 1 release.
* dotnet format
* PM-3275 - AcceptOrgUserCommand.cs - Move private method below where it is used
* PM-3275 - ServiceCollectionExtensions.cs - Must register IDataProtectorTokenFactory<OrgUserInviteTokenable> for new tokenable
* PM-3275 - OrgUserInviteTokenable needed access to global settings to set its token lifetime to the _globalSettings.OrganizationInviteExpirationHours value. Creating a factory seemed the most straightforward way to encapsulate the desired creation logic. Unsure if in the correct location in ServiceCollectionExtensions.cs but will figure that out later.
* PM-3275 - In process work of creating AcceptOrgUserCommandTests.cs
* PM-3275 - Remove no longer relevant AcceptOrgUser tests from OrganizationServiceTests.cs
* PM-3275 - Register OrgUserInviteTokenableFactory alongside tokenizer
* PM-3275 - AcceptOrgUserCommandTests.cs - AcceptOrgUserAsync basic test suite completed.
* PM-3275 - AcceptOrgUserCommandTests.cs - tweak test names
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Remove old tests from OrganizationServiceTests as no longer needed to reference (2) Add summary for SetupCommonAcceptOrgUserMocks (3) Get AcceptOrgUserByToken_OldToken_AcceptsUserAndVerifiesEmail passing
* PM-3275 - Create interface for OrgUserInviteTokenableFactory b/c that's the right thing to do + enables test substitution
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Start work on AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail (2) Create and use SetupCommonAcceptOrgUserByTokenMocks() (3) Create generic FakeDataProtectorTokenFactory for tokenable testing
* PM-3275 - (1) Get AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail test passing (2) Move FakeDataProtectorTokenFactory to own file
* PM-3275 - AcceptOrgUserCommandTests.cs - Finish up tests for AcceptOrgUserByTokenAsync
* PM-3275 - Add pseudo section comments
* PM-3275 - Clean up unused params on AcceptOrgUserByToken_EmailMismatch_ThrowsBadRequest test
* PM-3275 - (1) Tests written for AcceptOrgUserByOrgSsoIdAsync (2) Refactor happy path assertions into helper function AssertValidAcceptedOrgUser to reduce code duplication
* PM-3275 - Finish up testing AcceptOrgUserCommandTests.cs by adding tests for AcceptOrgUserByOrgIdAsync
* PM-3275 - Tweaking test naming to ensure consistency.
* PM-3275 - Bugfix - OrgUserInviteTokenableFactory implementation required when declaring singleton service in ServiceCollectionExtensions.cs
* PM-3275 - Resolve failing OrganizationServiceTests.cs
* dotnet format
* PM-3275 - PoliciesController.cs - GetMasterPasswordPolicy bugfix - for orgs without a MP policy, policy comes back as null and we should return notFound in that case.
* PM-3275 - Add PoliciesControllerTests.cs specifically for new GetMasterPasswordPolicy(...) endpoint.
* PM-3275 - dotnet format PoliciesControllerTests.cs
* PM-3275 - PoliciesController.cs - (1) Add tech debt task number (2) Properly flag endpoint as deprecated
* PM-3275 - Add new hasManageResetPasswordPermission property to ProfileResponseModel.cs primarily for sync so that we can condition client side if TDE user obtains elevated permissions
* PM-3275 - Fix AccountsControllerTests.cs
* PM-3275 - OrgUserInviteTokenable.cs - clarify TODO
* PM-3275 - AcceptOrgUserCommand.cs - Refactor token validation to use short circuiting to only run old token validation if new token validation fails.
* PM-3275 - OrgUserInviteTokenable.cs - (1) Add new static methods to centralize validation logic to avoid repetition (2) Add new token validation method so we can avoid having to pass in a full org user (and hitting the db to do so)
* PM-3275 - Realized that the old token validation was used in the PoliciesController.cs (existing user clicks invite link in email and goes to log in) and UserService.cs (user clicks invite link in email and registers for a new acct). Added tech debt item for cleaning up backwards compatibility in future.
* dotnet format
* PM-3275 - (1) AccountsController.cs - Update PostSetPasswordAsync SetPasswordRequestModel to allow null keys for the case where we have a TDE user who obtains elevated permissions - they already have a user public and user encrypted private key saved in the db. (2) AccountsControllerTests.cs - test PostSetPasswordAsync scenarios to ensure changes will work as expected.
* PM-3275 - PR review feedback - (1) set CurrentContext to private (2) Refactor GetProfile to use variables to improve clarity and simplify debugging.
* PM-3275 - SyncController.cs - PR Review Feedback - Set current context as private instead of protected.
* PM-3275 - CurrentContextExtensions.cs - PR Feedback - move parenthesis up from own line.
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - Replace unnecessary variable
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - PR Feedback - Add expected outcome statement to test name
* PM-3275 - Set Initial Password command and tests - PR Feedback changes - (1) Rename orgIdentifier --> OrgSsoIdentifier for clarity (2) Update SetInitialMasterPasswordAsync to not allow null orgSsoId with explicit message saying this vs letting null org trigger invalid organization (3) Add test to cover this new scenario.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Move summary from implementation to interface to better respect standards and the fact that the interface is the more seen piece of code.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, rename AcceptOrgUserByTokenAsync -> AcceptOrgUserByEmailTokenAsync + replace generic name token with emailToken
* PM-3275 - OrganizationService.cs - Per PR feedback, remove dupe line
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove new lines in error messages for consistency.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Per PR feedback, adjust formatting of constructor for improved readability.
* PM-3275 - CurrentContextExtensions.cs - Refactor AnyOrgUserHasManageResetPasswordPermission per PR feedback to remove unnecessary var.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove completed TODO
* PM-3275 - PoliciesController.cs - Per PR feedback, update GetByInvitedUser param to be guid instead of string.
* PM-3275 - OrgUserInviteTokenable.cs - per PR feedback, add tech debt item info.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, use const purpose from tokenable instead of magic string.
* PM-3275 - Restore non duplicate line to fix tests
* PM-3275 - Per PR feedback, revert all sync controller changes as the ProfileResponseModel.organizations array has org objects which have permissions which have the ManageResetPassword permission. So, I have the information that I need clientside already to determine if the user has the ManageResetPassword in any org.
* PM-3275 - PoliciesControllerTests.cs - Update imports as the PoliciesController was moved under the admin console team's domain.
* PM-3275 - Resolve issues from merge conflict resolutions to get solution building.
* PM-3275 / PM-4633 - PoliciesController.cs - use orgUserId to look up user instead of orgId. Oops.
* Fix user service tests
* Resolve merge conflict
2023-11-02 11:02:25 -04:00
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory ;
2024-10-01 09:12:08 -04:00
_featureService = featureService ;
_premiumUserBillingService = premiumUserBillingService ;
2024-10-16 10:33:00 +01:00
_removeOrganizationUserCommand = removeOrganizationUserCommand ;
2024-12-13 11:32:29 +00:00
_revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand ;
2025-01-09 18:10:54 -08:00
_distributedCache = distributedCache ;
2017-01-24 22:15:21 -05:00
}
public Guid ? GetProperUserId ( ClaimsPrincipal principal )
2022-08-29 16:06:55 -04:00
{
2017-01-24 22:15:21 -05:00
if ( ! Guid . TryParse ( GetUserId ( principal ) , out var userIdGuid ) )
2022-08-29 14:53:16 -04:00
{
2017-01-24 22:15:21 -05:00
return null ;
2022-08-29 15:53:48 -04:00
}
2016-05-21 17:16:22 -04:00
return userIdGuid ;
2022-08-29 15:53:48 -04:00
}
2017-01-24 22:46:54 -05:00
public async Task < User > GetUserByIdAsync ( string userId )
2022-08-29 16:06:55 -04:00
{
2017-01-24 22:46:54 -05:00
if ( _currentContext ? . User ! = null & &
string . Equals ( _currentContext . User . Id . ToString ( ) , userId , StringComparison . InvariantCultureIgnoreCase ) )
2022-08-29 16:06:55 -04:00
{
2017-01-24 22:15:21 -05:00
return _currentContext . User ;
2017-01-11 21:46:36 -05:00
}
2016-05-21 17:16:22 -04:00
if ( ! Guid . TryParse ( userId , out var userIdGuid ) )
2015-12-08 22:57:38 -05:00
{
2017-01-24 22:46:54 -05:00
return null ;
}
2021-05-19 09:40:32 -05:00
_currentContext . User = await _userRepository . GetByIdAsync ( userIdGuid ) ;
return _currentContext . User ;
2022-08-29 14:53:16 -04:00
}
2017-01-14 10:02:37 -05:00
public async Task < User > GetUserByIdAsync ( Guid userId )
2022-08-29 16:06:55 -04:00
{
2017-01-24 22:46:54 -05:00
if ( _currentContext ? . User ! = null & & _currentContext . User . Id = = userId )
2022-08-29 16:06:55 -04:00
{
2017-01-14 10:02:37 -05: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 ) ;
2021-05-19 09:40:32 -05:00
if ( ! userId . HasValue )
2015-12-08 22:57:38 -05:00
{
return null ;
}
2016-02-21 00:15:17 -05:00
2021-05-19 09:40:32 -05:00
return await GetUserByIdAsync ( userId . Value ) ;
2022-08-29 16:06:55 -04:00
}
2017-08-16 17:08:20 -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
{
2020-03-27 14:36:37 -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." ) ;
2022-08-29 15:53:48 -04:00
}
2023-01-25 11:07:33 -05:00
// if the name is empty, set it to null
if ( String . Equals ( user . Name , String . Empty ) )
{
user . Name = null ;
}
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 ) ;
2022-08-29 15:53:48 -04:00
2020-03-27 14:36:37 -04:00
if ( push )
2022-08-29 15:53:48 -04:00
{
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
2017-04-27 17:28:39 -04:00
public override async Task < IdentityResult > DeleteAsync ( User user )
2022-08-29 16:06:55 -04:00
{
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
{
2017-10-25 11:36:54 -04: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 ) ;
if ( org ! = null & & ( ! org . Enabled | | string . IsNullOrWhiteSpace ( org . GatewaySubscriptionId ) ) )
2017-04-27 17:28:39 -04:00
{
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 ;
}
}
2017-04-27 17:28:39 -04:00
}
2021-09-15 20:34:06 +02:00
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
}
2021-09-15 20:34:06 +02:00
2020-03-27 14:36:37 -04:00
var onlyOwnerProviderCount = await _providerUserRepository . GetCountByOnlyOwnerAsync ( user . Id ) ;
if ( onlyOwnerProviderCount > 0 )
2022-08-29 16:06:55 -04:00
{
2020-03-27 14:36:37 -04:00
return IdentityResult . Failed ( new IdentityError
2017-07-11 11:19:58 -04:00
{
2017-04-27 17:28:39 -04:00
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 ) )
{
2020-03-27 14:36:37 -04:00
try
2017-08-09 10:53:42 -04:00
{
2024-01-11 15:26:32 -05:00
await CancelPremiumAsync ( user ) ;
2017-08-09 10:53:42 -04:00
}
catch ( GatewayException ) { }
2022-08-29 14:53:16 -04:00
}
2017-08-09 10:53:42 -04:00
await _userRepository . DeleteAsync ( user ) ;
2017-04-27 17:28:39 -04:00
await _referenceEventService . RaiseEventAsync (
2023-05-16 16:21:57 +02:00
new ReferenceEvent ( ReferenceEventType . DeleteAccount , user , _currentContext ) ) ;
2017-04-27 17:28:39 -04:00
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
}
2018-07-31 08:19:49 -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 ;
2018-05-24 16:53:07 -04:00
}
2024-11-04 16:37:21 +00:00
if ( await IsManagedByAnyOrganizationAsync ( user . Id ) )
{
await _mailService . SendCannotDeleteManagedAccountEmailAsync ( user . Email ) ;
return ;
}
2017-06-20 10:08:59 -04:00
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
}
2024-07-02 17:03:36 -04:00
public async Task < IdentityResult > CreateUserAsync ( User user )
2022-08-29 16:06:55 -04:00
{
2024-07-02 17:03:36 -04:00
return await CreateAsync ( user ) ;
2022-08-29 16:06:55 -04:00
}
2024-07-02 17:03:36 -04:00
public async Task < IdentityResult > CreateUserAsync ( User user , string masterPasswordHash )
2022-08-29 16:06:55 -04:00
{
2024-07-02 17:03:36 -04:00
return await CreateAsync ( user , masterPasswordHash ) ;
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
}
2023-02-17 10:15:28 -03: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." ) ;
}
2020-03-27 14:36:37 -04:00
var email = ( ( string ) provider . MetaData [ "Email" ] ) . ToLowerInvariant ( ) ;
2024-07-11 14:39:27 -04:00
var token = await base . GenerateTwoFactorTokenAsync ( user ,
CoreHelpers . CustomProviderName ( TwoFactorProviderType . Email ) ) ;
2023-02-17 10:15:28 -03:00
await _mailService . SendTwoFactorEmailAsync ( email , token ) ;
2022-08-29 16:06:55 -04:00
}
2015-12-08 22:57:38 -05:00
2022-04-01 17:08:47 -03:00
public async Task < bool > VerifyTwoFactorEmailAsync ( User user , string token )
2022-08-29 16:06:55 -04:00
{
2022-04-01 17:08:47 -03: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
{
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 ( ) ;
2024-07-11 14:39:27 -04:00
return await base . VerifyTwoFactorTokenAsync ( user ,
CoreHelpers . CustomProviderName ( TwoFactorProviderType . Email ) , token ) ;
2022-08-29 16:06:55 -04:00
}
2022-04-01 17:08:47 -03: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 ( ) ;
2021-05-12 18:46:35 +02:00
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
2022-08-29 15:53:48 -04:00
{
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 > ( ) ;
2022-08-29 15:53:48 -04:00
}
2017-06-21 22:33:45 -04:00
2021-05-12 18:46:35 +02: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 ( ) ,
2022-08-29 15:53:48 -04:00
} ;
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
2021-05-12 18:46:35 +02:00
var authenticatorSelection = new AuthenticatorSelection
2022-08-29 16:06:55 -04:00
{
2021-03-22 23:21:43 +01:00
AuthenticatorAttachment = null ,
2021-05-12 18:46:35 +02:00
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" ] ) ;
2023-01-19 11:06:51 -05:00
// 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 ,
2020-03-27 14:36:37 -04:00
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
2020-03-27 14:36:37 -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 )
2022-08-29 15:53:48 -04:00
{
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 ) ;
2020-03-27 14:36:37 -04:00
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 ;
2022-08-29 15:53:48 -04:00
}
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
{
2016-02-21 00:15:17 -05:00
return result ;
2022-08-29 16:06:55 -04:00
}
2023-02-02 14:39:57 -05:00
var now = DateTime . UtcNow ;
2017-05-31 09:54:32 -04:00
user . Key = key ;
user . Email = newEmail ;
user . EmailVerified = true ;
2023-02-02 14:39:57 -05:00
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
2022-08-29 15:53:48 -04:00
{
2015-12-08 22:57:38 -05:00
await _stripeSyncService . UpdateCustomerEmailAddress ( user . GatewayCustomerId ,
user . BillingEmailAddress ( ) ) ;
}
2016-02-21 00:15:17 -05:00
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
2022-06-16 17:45:26 +01:00
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 ;
2022-06-16 17:45:26 +01:00
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
2022-07-11 09:28:14 -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
}
2022-07-11 09:28:14 -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
{
2016-02-21 00:15:17 -05: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
}
2022-07-11 09:28:14 -04:00
if ( await CheckPasswordAsync ( user , masterPassword ) )
2022-08-29 16:06:55 -04: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 )
2016-02-21 00:15:17 -05:00
{
return result ;
}
2015-12-08 22:57:38 -05:00
2023-02-02 14:39:57 -05:00
var now = DateTime . UtcNow ;
user . RevisionDate = user . AccountRevisionDate = now ;
user . LastPasswordChangeDate = now ;
2022-06-16 17:45:26 +01:00
user . Key = key ;
2017-12-01 14:06:16 -05:00
user . MasterPasswordHint = passwordHint ;
2022-08-29 15:53:48 -04:00
2017-05-31 09:54:32 -04:00
await _userRepository . ReplaceAsync ( user ) ;
await _eventService . LogUserEventAsync ( user . Id , EventType . User_ChangedPassword ) ;
2022-11-24 11:25:16 -05:00
await _pushService . PushLogOutAsync ( user . Id , true ) ;
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
Logger . LogWarning ( "Change password failed for user {userId}." , user . Id ) ;
return IdentityResult . Failed ( _identityErrorDescriber . PasswordMismatch ( ) ) ;
2022-08-29 16:06:55 -04:00
}
2020-09-14 14:27:30 -05: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
{
2020-09-14 14:27:30 -05:00
return identityResult ;
2022-08-29 15:53:48 -04:00
}
2020-08-12 17:03:09 -04:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
2021-11-09 16:37:32 +01:00
user . Key = key ;
2020-08-12 17:03:09 -04:00
user . UsesKeyConnector = true ;
await _userRepository . ReplaceAsync ( user ) ;
await _eventService . LogUserEventAsync ( user . Id , EventType . User_MigratedKeyToKeyConnector ) ;
2021-11-09 16:37:32 +01:00
Auth/PM-3275 - Changes to support TDE User without MP being able to Set a Password + misc refactoring (#3242)
* PM-3275 - Add new GetMasterPasswordPolicy endpoint which will allow authenticated clients to get an enabled MP org policy if it exists for the purposes of enforcing those policy requirements when setting a password.
* PM-3275 - AccountsController.cs - PostSetPasswordAsync - (1) Convert UserService.setPasswordAsync into new SetInitialMasterPasswordCommand (2) Refactor SetInitialMasterPasswordCommand to only accept post SSO users who are in the invited state
(3) Add TODOs for more cleanup work and more commands
* PM-3275 - Update AccountsControllerTests.cs to add new SetInitialMasterPasswordCommand
* PM-3275 - UserService.cs - Remove non implemented ChangePasswordAsync method
* PM-3275 - The new SetInitialMasterPasswordCommand leveraged the OrganizationService.cs AcceptUserAsync method so while I was in here I converted the AcceptUserAsync methods into a new AcceptOrgUserCommand.cs and turned the private method which accepted an existing org user public for use in the SetInitialMasterPasswordCommand
* PM-3275 - Dotnet format
* PM-3275 - Test SetInitialMasterPasswordCommand
* Dotnet format
* PM-3275 - In process AcceptOrgUserCommandTests.cs
* PM-3275 - Migrate changes from AC-244 / #3199 over into new AcceptOrgUserCommand
* PM-3275 - AcceptOrgUserCommand.cs - create data protector specifically for this command
* PM-3275 - Add TODO for renaming / removing overloading of methods to improve readability / clarity
* PM-3275 - AcceptOrgUserCommand.cs - refactor AcceptOrgUserAsync by OrgId to retrieve orgUser with _organizationUserRepository.GetByOrganizationAsync which gets a single user instead of a collection
* PM-3275 - AcceptOrgUserCommand.cs - update name in TODO for evaluation later
* PM-3275 / PM-1196 - (1) Slightly refactor SsoEmail2faSessionTokenable to provide public static GetTokenLifeTime() method for testing (2) Add missed tests to SsoEmail2faSessionTokenable in preparation for building tests for new OrgUserInviteTokenable.cs
* PM-3275 / PM-1196 - Removing SsoEmail2faSessionTokenable.cs changes + tests as I've handled that separately in a new PR (#3270) for newly created task PM-3925
* PM-3275 - ExpiringTokenable.cs - add clarifying comments to help distinguish between the Valid property and the TokenIsValid method.
* PM-3275 - Create OrgUserInviteTokenable.cs and add tests in OrgUserInviteTokenableTests.cs
* PM-3275 - OrganizationService.cs - Refactor Org User Invite methods to use new OrgUserInviteTokenable instead of manual creation of a token
* PM-3275 - OrgUserInviteTokenable.cs - clarify backwards compat note
* PM-3275 - AcceptOrgUserCommand.cs - Add TODOs + minor name refactor
* PM-3275 - AcceptOrgUserCommand.cs - replace method overloading with more easily readable names.
* PM-3275 - AcceptOrgUserCommand.cs - Update ValidateOrgUserInviteToken to add new token validation while maintaining backwards compatibility for 1 release.
* dotnet format
* PM-3275 - AcceptOrgUserCommand.cs - Move private method below where it is used
* PM-3275 - ServiceCollectionExtensions.cs - Must register IDataProtectorTokenFactory<OrgUserInviteTokenable> for new tokenable
* PM-3275 - OrgUserInviteTokenable needed access to global settings to set its token lifetime to the _globalSettings.OrganizationInviteExpirationHours value. Creating a factory seemed the most straightforward way to encapsulate the desired creation logic. Unsure if in the correct location in ServiceCollectionExtensions.cs but will figure that out later.
* PM-3275 - In process work of creating AcceptOrgUserCommandTests.cs
* PM-3275 - Remove no longer relevant AcceptOrgUser tests from OrganizationServiceTests.cs
* PM-3275 - Register OrgUserInviteTokenableFactory alongside tokenizer
* PM-3275 - AcceptOrgUserCommandTests.cs - AcceptOrgUserAsync basic test suite completed.
* PM-3275 - AcceptOrgUserCommandTests.cs - tweak test names
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Remove old tests from OrganizationServiceTests as no longer needed to reference (2) Add summary for SetupCommonAcceptOrgUserMocks (3) Get AcceptOrgUserByToken_OldToken_AcceptsUserAndVerifiesEmail passing
* PM-3275 - Create interface for OrgUserInviteTokenableFactory b/c that's the right thing to do + enables test substitution
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Start work on AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail (2) Create and use SetupCommonAcceptOrgUserByTokenMocks() (3) Create generic FakeDataProtectorTokenFactory for tokenable testing
* PM-3275 - (1) Get AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail test passing (2) Move FakeDataProtectorTokenFactory to own file
* PM-3275 - AcceptOrgUserCommandTests.cs - Finish up tests for AcceptOrgUserByTokenAsync
* PM-3275 - Add pseudo section comments
* PM-3275 - Clean up unused params on AcceptOrgUserByToken_EmailMismatch_ThrowsBadRequest test
* PM-3275 - (1) Tests written for AcceptOrgUserByOrgSsoIdAsync (2) Refactor happy path assertions into helper function AssertValidAcceptedOrgUser to reduce code duplication
* PM-3275 - Finish up testing AcceptOrgUserCommandTests.cs by adding tests for AcceptOrgUserByOrgIdAsync
* PM-3275 - Tweaking test naming to ensure consistency.
* PM-3275 - Bugfix - OrgUserInviteTokenableFactory implementation required when declaring singleton service in ServiceCollectionExtensions.cs
* PM-3275 - Resolve failing OrganizationServiceTests.cs
* dotnet format
* PM-3275 - PoliciesController.cs - GetMasterPasswordPolicy bugfix - for orgs without a MP policy, policy comes back as null and we should return notFound in that case.
* PM-3275 - Add PoliciesControllerTests.cs specifically for new GetMasterPasswordPolicy(...) endpoint.
* PM-3275 - dotnet format PoliciesControllerTests.cs
* PM-3275 - PoliciesController.cs - (1) Add tech debt task number (2) Properly flag endpoint as deprecated
* PM-3275 - Add new hasManageResetPasswordPermission property to ProfileResponseModel.cs primarily for sync so that we can condition client side if TDE user obtains elevated permissions
* PM-3275 - Fix AccountsControllerTests.cs
* PM-3275 - OrgUserInviteTokenable.cs - clarify TODO
* PM-3275 - AcceptOrgUserCommand.cs - Refactor token validation to use short circuiting to only run old token validation if new token validation fails.
* PM-3275 - OrgUserInviteTokenable.cs - (1) Add new static methods to centralize validation logic to avoid repetition (2) Add new token validation method so we can avoid having to pass in a full org user (and hitting the db to do so)
* PM-3275 - Realized that the old token validation was used in the PoliciesController.cs (existing user clicks invite link in email and goes to log in) and UserService.cs (user clicks invite link in email and registers for a new acct). Added tech debt item for cleaning up backwards compatibility in future.
* dotnet format
* PM-3275 - (1) AccountsController.cs - Update PostSetPasswordAsync SetPasswordRequestModel to allow null keys for the case where we have a TDE user who obtains elevated permissions - they already have a user public and user encrypted private key saved in the db. (2) AccountsControllerTests.cs - test PostSetPasswordAsync scenarios to ensure changes will work as expected.
* PM-3275 - PR review feedback - (1) set CurrentContext to private (2) Refactor GetProfile to use variables to improve clarity and simplify debugging.
* PM-3275 - SyncController.cs - PR Review Feedback - Set current context as private instead of protected.
* PM-3275 - CurrentContextExtensions.cs - PR Feedback - move parenthesis up from own line.
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - Replace unnecessary variable
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - PR Feedback - Add expected outcome statement to test name
* PM-3275 - Set Initial Password command and tests - PR Feedback changes - (1) Rename orgIdentifier --> OrgSsoIdentifier for clarity (2) Update SetInitialMasterPasswordAsync to not allow null orgSsoId with explicit message saying this vs letting null org trigger invalid organization (3) Add test to cover this new scenario.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Move summary from implementation to interface to better respect standards and the fact that the interface is the more seen piece of code.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, rename AcceptOrgUserByTokenAsync -> AcceptOrgUserByEmailTokenAsync + replace generic name token with emailToken
* PM-3275 - OrganizationService.cs - Per PR feedback, remove dupe line
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove new lines in error messages for consistency.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Per PR feedback, adjust formatting of constructor for improved readability.
* PM-3275 - CurrentContextExtensions.cs - Refactor AnyOrgUserHasManageResetPasswordPermission per PR feedback to remove unnecessary var.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove completed TODO
* PM-3275 - PoliciesController.cs - Per PR feedback, update GetByInvitedUser param to be guid instead of string.
* PM-3275 - OrgUserInviteTokenable.cs - per PR feedback, add tech debt item info.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, use const purpose from tokenable instead of magic string.
* PM-3275 - Restore non duplicate line to fix tests
* PM-3275 - Per PR feedback, revert all sync controller changes as the ProfileResponseModel.organizations array has org objects which have permissions which have the ManageResetPassword permission. So, I have the information that I need clientside already to determine if the user has the ManageResetPassword in any org.
* PM-3275 - PoliciesControllerTests.cs - Update imports as the PoliciesController was moved under the admin console team's domain.
* PM-3275 - Resolve issues from merge conflict resolutions to get solution building.
* PM-3275 / PM-4633 - PoliciesController.cs - use orgUserId to look up user instead of orgId. Oops.
* Fix user service tests
* Resolve merge conflict
2023-11-02 11:02:25 -04:00
await _acceptOrgUserCommand . AcceptOrgUserByOrgSsoIdAsync ( orgIdentifier , user , this ) ;
2021-11-09 16:37:32 +01:00
2020-08-12 17:03:09 -04:00
return IdentityResult . Success ;
2022-08-29 15:53:48 -04:00
}
2020-08-12 17:03:09 -04:00
public async Task < IdentityResult > ConvertToKeyConnectorAsync ( User user )
2022-08-29 16:06:55 -04:00
{
2021-11-18 21:56:13 +01:00
var identityResult = CheckCanUseKeyConnector ( user ) ;
if ( identityResult ! = null )
2022-08-29 16:06:55 -04:00
{
2020-08-12 17:03:09 -04:00
return identityResult ;
}
2021-10-25 15:09:14 +02:00
2021-11-09 16:37:32 +01:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . MasterPassword = null ;
user . UsesKeyConnector = true ;
2021-10-25 15:09:14 +02:00
2021-11-09 16:37:32 +01:00
await _userRepository . ReplaceAsync ( user ) ;
await _eventService . LogUserEventAsync ( user . Id , EventType . User_MigratedKeyToKeyConnector ) ;
2021-10-25 15:09:14 +02:00
2021-11-09 16:37:32 +01: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 ( ) ) ;
}
2021-11-09 16:37:32 +01:00
if ( _currentContext . Organizations . Any ( u = >
u . Type is OrganizationUserType . Owner or OrganizationUserType . Admin ) )
2021-11-18 21:56:13 +01:00
{
throw new BadRequestException ( "Cannot use Key Connector when admin or owner of an organization." ) ;
}
return null ;
2022-08-29 16:06:55 -04:00
}
2021-11-18 21:56:13 +01:00
public async Task < IdentityResult > AdminResetPasswordAsync ( OrganizationUserType callingUserType , Guid orgId , Guid id , string newMasterPassword , string key )
2022-08-29 16:06:55 -04:00
{
2021-11-18 21:56:13 +01: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
{
2021-11-18 21:56:13 +01:00
throw new BadRequestException ( "Organization does not allow password reset." ) ;
2022-08-29 16:06:55 -04:00
}
2021-11-18 21:56:13 +01:00
// Enterprise policy must be enabled
2021-05-19 09:40:32 -05:00
var resetPasswordPolicy =
2021-11-18 21:56:13 +01:00
await _policyRepository . GetByOrganizationIdTypeAsync ( orgId , PolicyType . ResetPassword ) ;
if ( resetPasswordPolicy = = null | | ! resetPasswordPolicy . Enabled )
2022-08-29 16:06:55 -04:00
{
2021-11-18 21:56:13 +01: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 ) | |
2021-05-19 09:40:32 -05:00
! orgUser . UserId . HasValue )
2021-11-09 16:37:32 +01:00
{
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
2021-11-09 16:37:32 +01:00
var canAdjustPassword = false ;
switch ( callingUserType )
2022-08-29 16:06:55 -04:00
{
2021-11-09 16:37:32 +01:00
case OrganizationUserType . Owner :
2021-05-19 09:40:32 -05:00
canAdjustPassword = true ;
2021-11-09 16:37:32 +01:00
break ;
2021-05-19 09:40:32 -05:00
case OrganizationUserType . Admin :
2021-11-09 16:37:32 +01:00
canAdjustPassword = orgUser . Type ! = OrganizationUserType . Owner ;
break ;
2021-05-19 09:40:32 -05:00
case OrganizationUserType . Custom :
canAdjustPassword = orgUser . Type ! = OrganizationUserType . Owner & &
orgUser . Type ! = OrganizationUserType . Admin ;
2021-11-09 16:37:32 +01:00
break ;
}
2021-11-18 21:56:13 +01:00
if ( ! canAdjustPassword )
2022-08-29 15:53:48 -04:00
{
2021-11-18 21:56:13 +01:00
throw new BadRequestException ( "Calling user does not have permission to reset this user's master password" ) ;
}
2021-11-09 16:37:32 +01:00
2021-11-18 21:56:13 +01:00
var user = await GetUserByIdAsync ( orgUser . UserId . Value ) ;
if ( user = = null )
2022-08-29 16:06:55 -04:00
{
2021-05-19 09:40:32 -05:00
throw new NotFoundException ( ) ;
2021-11-09 16:37:32 +01:00
}
2021-05-19 09:40:32 -05:00
if ( user . UsesKeyConnector )
2021-04-20 16:58:57 -05:00
{
2021-05-19 09:40:32 -05:00
throw new BadRequestException ( "Cannot reset password of a user with Key Connector." ) ;
}
2021-11-09 16:37:32 +01:00
2021-05-19 09:40:32 -05:00
var result = await UpdatePasswordHash ( user , newMasterPassword ) ;
if ( ! result . Succeeded )
{
return result ;
}
2021-11-09 16:37:32 +01:00
2021-05-19 09:40:32 -05:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
2024-08-20 16:20:56 -07:00
user . LastPasswordChangeDate = user . RevisionDate ;
2021-05-19 09:40:32 -05:00
user . ForcePasswordReset = true ;
2024-08-20 16:20:56 -07:00
user . Key = key ;
2021-11-09 16:37:32 +01:00
2021-05-19 09:40:32 -05:00
await _userRepository . ReplaceAsync ( user ) ;
2024-03-05 10:56:48 +00:00
await _mailService . SendAdminResetPasswordEmailAsync ( user . Email , user . Name , org . DisplayName ( ) ) ;
2021-05-19 09:40:32 -05:00
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
{
2021-05-19 09:40:32 -05:00
if ( ! user . ForcePasswordReset )
{
throw new BadRequestException ( "User does not have a temporary password to update." ) ;
}
2021-11-09 16:37:32 +01:00
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 ;
2021-11-09 16:37:32 +01:00
}
2021-04-20 16:58:57 -05:00
user . RevisionDate = user . AccountRevisionDate = DateTime . UtcNow ;
user . ForcePasswordReset = false ;
2021-07-22 09:20:14 -05:00
user . Key = key ;
2021-04-20 16:58:57 -05:00
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 ) ;
2021-05-26 15:51:54 -05:00
return IdentityResult . Success ;
2022-08-29 16:06:55 -04:00
}
2021-04-20 16:58:57 -05:00
public async Task < IdentityResult > ChangeKdfAsync ( User user , string masterPassword , string newMasterPassword ,
2023-01-25 13:56:54 +01:00
string key , KdfType kdf , int kdfIterations , int? kdfMemory , int? kdfParallelism )
2022-08-29 16:06:55 -04:00
{
2020-03-27 14:36:37 -04:00
if ( user = = null )
2022-08-29 16:06:55 -04:00
{
2021-04-20 16:58:57 -05:00
throw new ArgumentNullException ( nameof ( user ) ) ;
}
2021-11-09 16:37:32 +01:00
2021-08-05 13:00:24 -05:00
if ( await CheckPasswordAsync ( user , masterPassword ) )
2021-07-22 09:20:14 -05:00
{
var result = await UpdatePasswordHash ( user , newMasterPassword ) ;
if ( ! result . Succeeded )
{
return result ;
}
2023-02-02 14:39:57 -05:00
var now = DateTime . UtcNow ;
user . RevisionDate = user . AccountRevisionDate = now ;
user . LastKdfChangeDate = now ;
2021-07-22 09:20:14 -05:00
user . Key = key ;
2018-08-14 15:30:04 -04:00
user . Kdf = kdf ;
2021-08-05 13:00:24 -05:00
user . KdfIterations = kdfIterations ;
2023-01-25 13:56:54 +01:00
user . KdfMemory = kdfMemory ;
user . KdfParallelism = kdfParallelism ;
2021-07-22 09:20:14 -05:00
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 ) ;
2021-07-22 09:20:14 -05:00
return IdentityResult . Failed ( _identityErrorDescriber . PasswordMismatch ( ) ) ;
2022-08-29 16:06:55 -04:00
}
2022-04-28 16:42:47 -05:00
public async Task < IdentityResult > RefreshSecurityStampAsync ( User user , string secret )
2022-08-29 16:06:55 -04:00
{
2022-04-28 16:42:47 -05:00
if ( user = = null )
2017-06-07 14:14:34 -04:00
{
2021-03-22 23:21:43 +01:00
throw new ArgumentNullException ( nameof ( user ) ) ;
2017-06-19 22:08:10 -04:00
}
2020-02-19 14:56:16 -05:00
if ( await VerifySecretAsync ( user , secret ) )
2017-06-19 22:08:10 -04:00
{
var result = await base . UpdateSecurityStampAsync ( user ) ;
2020-03-27 14:36:37 -04:00
if ( ! result . Succeeded )
2017-06-19 22:08:10 -04:00
{
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 ) ;
2016-11-14 23:32:15 -05:00
return IdentityResult . Success ;
2022-08-29 14:53:16 -04:00
}
2016-11-14 23:32:15 -05:00
2021-11-09 16:37:32 +01: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
}
2021-11-09 16:37:32 +01: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 )
2016-11-14 23:32:15 -05:00
{
2020-06-17 19:49:27 -04:00
await _eventService . LogUserEventAsync ( user . Id , EventType . User_Updated2fa ) ;
2022-08-29 16:06:55 -04:00
}
2016-11-14 23:32:15 -05:00
}
2024-10-16 10:33:00 +01:00
public async Task DisableTwoFactorProviderAsync ( User user , TwoFactorProviderType type )
2022-08-29 16:06:55 -04:00
{
2021-11-08 15:55:42 -05:00
var providers = user . GetTwoFactorProviders ( ) ;
if ( ! providers ? . ContainsKey ( type ) ? ? true )
2022-08-29 15:53:48 -04:00
{
2021-11-08 15:55:42 -05:00
return ;
2022-08-29 15:53:48 -04:00
}
2016-11-14 23:32:15 -05:00
2019-12-23 15:26:39 -05:00
providers . Remove ( type ) ;
user . SetTwoFactorProviders ( providers ) ;
2016-02-21 01:10:31 -05:00
await SaveUserAsync ( user ) ;
2019-12-23 15:26:39 -05:00
await _eventService . LogUserEventAsync ( user . Id , EventType . User_Disabled2fa ) ;
2022-08-29 15:53:48 -04:00
2019-12-23 15:26:39 -05:00
if ( ! await TwoFactorIsEnabledAsync ( user ) )
2022-08-29 16:06:55 -04:00
{
2024-10-16 10:33:00 +01:00
await CheckPoliciesOnTwoFactorRemovalAsync ( user ) ;
2016-11-14 23:32:15 -05:00
}
2022-08-29 16:06:55 -04:00
}
2016-11-14 23:32:15 -05:00
2024-10-16 10:33:00 +01:00
public async Task < bool > RecoverTwoFactorAsync ( string email , string secret , string recoveryCode )
2022-08-29 16:06:55 -04:00
{
2020-06-17 19:49:27 -04:00
var user = await _userRepository . GetByEmailAsync ( email ) ;
2020-06-18 10:41:55 -04:00
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 ;
2022-08-29 15:53:48 -04:00
}
2017-08-11 22:55:25 -04:00
if ( ! await VerifySecretAsync ( user , secret ) )
2022-08-29 16:06:55 -04:00
{
2019-08-09 23:56:26 -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 ;
2022-08-29 15:53:48 -04:00
}
2019-09-19 08:46:26 -04:00
2020-06-18 10:41:55 -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 ) ;
2024-10-16 10:33:00 +01:00
await CheckPoliciesOnTwoFactorRemovalAsync ( user ) ;
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 ,
2020-06-18 10:41:55 -04:00
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." ) ;
}
2017-08-14 12:08:57 -04:00
if ( additionalStorageGb < 0 )
{
throw new BadRequestException ( "You can't subtract storage!" ) ;
}
2019-09-18 10:52:53 -04:00
string paymentIntentClientSecret = null ;
2017-07-28 12:09:12 -04:00
IPaymentService paymentService = null ;
2019-09-18 10:52:53 -04:00
if ( _globalSettings . SelfHosted )
2022-08-29 16:06:55 -04:00
{
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." ) ;
}
2024-12-05 09:31:14 -05:00
var claimsPrincipal = _licenseService . GetClaimsPrincipalFromLicense ( license ) ;
if ( ! license . CanUse ( user , claimsPrincipal , out var exceptionMessage ) )
2017-08-16 15:43:11 -04:00
{
2024-08-26 14:12:58 +01:00
throw new BadRequestException ( exceptionMessage ) ;
2017-08-16 15:43:11 -04:00
}
2017-08-14 12:08:57 -04:00
var dir = $"{_globalSettings.LicenseDirectory}/user" ;
Directory . CreateDirectory ( dir ) ;
2022-01-21 09:36:25 -05: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
}
else
{
2024-10-01 09:12:08 -04:00
var deprecateStripeSourcesAPI = _featureService . IsEnabled ( FeatureFlagKeys . AC2476_DeprecateStripeSourcesAPI ) ;
if ( deprecateStripeSourcesAPI )
{
var sale = PremiumUserSale . From ( user , paymentMethodType , paymentToken , taxInfo , additionalStorageGb ) ;
await _premiumUserBillingService . Finalize ( sale ) ;
}
else
{
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
2017-08-16 15:43:11 -04:00
user . Premium = true ;
user . RevisionDate = DateTime . UtcNow ;
2022-08-29 16:06:55 -04:00
2017-08-16 15:43:11 -04:00
if ( _globalSettings . SelfHosted )
2022-08-29 16:06:55 -04:00
{
2017-08-16 15:43:11 -04:00
user . MaxStorageGb = 10240 ; // 10 TB
2017-08-14 12:08:57 -04:00
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
{
2019-08-10 12:59:32 -04:00
user . MaxStorageGb = ( short ) ( 1 + additionalStorageGb ) ;
user . LicenseKey = CoreHelpers . SecureRandomString ( 20 ) ;
2022-08-29 15:53:48 -04:00
}
2022-08-29 16:06:55 -04:00
try
{
2019-08-10 12:59:32 -04:00
await SaveUserAsync ( user ) ;
await _pushService . PushSyncVaultAsync ( user . Id ) ;
2020-07-07 12:01:34 -04:00
await _referenceEventService . RaiseEventAsync (
2023-05-16 16:21:57 +02:00
new ReferenceEvent ( ReferenceEventType . UpgradePlan , user , _currentContext )
2020-07-07 12:01:34 -04:00
{
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 ;
}
2025-01-02 20:27:53 +01:00
2019-08-09 23:56:26 -04:00
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
2019-08-09 23:56:26 -04:00
public async Task UpdateLicenseAsync ( User user , UserLicense license )
2022-08-29 16:06:55 -04:00
{
2019-08-09 23:56:26 -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." ) ;
2017-08-16 17:08:20 -04:00
}
2024-12-05 09:31:14 -05:00
var claimsPrincipal = _licenseService . GetClaimsPrincipalFromLicense ( license ) ;
if ( ! license . CanUse ( user , claimsPrincipal , out var exceptionMessage ) )
2017-08-16 17:08:20 -04:00
{
2024-08-26 14:12:58 +01:00
throw new BadRequestException ( exceptionMessage ) ;
2017-08-12 22:16:42 -04:00
}
var dir = $"{_globalSettings.LicenseDirectory}/user" ;
2017-08-14 12:08:57 -04:00
Directory . CreateDirectory ( dir ) ;
2017-08-12 22:16:42 -04:00
using var fs = File . OpenWrite ( Path . Combine ( dir , $"{user.Id}.json" ) ) ;
2022-01-21 09:36:25 -05:00
await JsonSerializer . SerializeAsync ( fs , license , JsonHelpers . Indented ) ;
2022-08-29 16:06:55 -04:00
2017-08-14 12:08:57 -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
2017-08-14 12:08:57 -04:00
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
{
2019-08-10 12:59:32 -04:00
if ( user = = null )
2017-08-12 22:16:42 -04:00
{
2019-08-10 12:59:32 -04:00
throw new ArgumentNullException ( nameof ( user ) ) ;
2017-08-30 11:23:55 -04:00
}
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." ) ;
2017-08-30 11:23:55 -04:00
}
2018-04-17 08:10:17 -04:00
var secret = await BillingHelpers . AdjustStorageAsync ( _paymentService , user , storageAdjustmentGb ,
StoragePlanId ) ;
await _referenceEventService . RaiseEventAsync (
2023-05-16 16:21:57 +02:00
new ReferenceEvent ( ReferenceEventType . AdjustStorage , user , _currentContext )
2018-04-17 08:10:17 -04:00
{
2019-08-10 12:59:32 -04:00
Storage = storageAdjustmentGb ,
2020-07-07 12:01:34 -04:00
PlanName = StoragePlanId ,
} ) ;
2017-08-11 22:55:25 -04:00
await SaveUserAsync ( user ) ;
2019-08-09 23:56:26 -04:00
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
2024-01-11 15:26:32 -05:00
public async Task CancelPremiumAsync ( User user , bool? endOfPeriod = null )
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 & &
2020-07-07 12:01:34 -04:00
user . PremiumExpirationDate . Value < DateTime . UtcNow )
2022-08-29 14:53:16 -04:00
{
2019-08-09 23:56:26 -04:00
eop = false ;
2022-08-29 16:06:55 -04:00
}
2024-01-11 15:26:32 -05:00
await _paymentService . CancelSubscriptionAsync ( user , eop ) ;
2020-07-07 12:01:34 -04:00
await _referenceEventService . RaiseEventAsync (
2023-05-16 16:21:57 +02:00
new ReferenceEvent ( ReferenceEventType . CancelSubscription , user , _currentContext )
2022-08-29 15:53:48 -04:00
{
2024-01-11 15:26:32 -05:00
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 )
2022-08-29 15:53:48 -04:00
{
2017-08-16 17:08:20 -04:00
await _paymentService . ReinstateSubscriptionAsync ( user ) ;
await _referenceEventService . RaiseEventAsync (
2023-05-16 16:21:57 +02:00
new ReferenceEvent ( ReferenceEventType . ReinstateSubscription , user , _currentContext ) ) ;
2022-08-29 14:53:16 -04:00
}
2018-04-17 08:10:17 -04:00
2018-12-19 11:48:36 -05:00
public async Task EnablePremiumAsync ( Guid userId , DateTime ? expirationDate )
2018-08-28 16:23:58 -04:00
{
2020-03-27 14:36:37 -04:00
var user = await _userRepository . GetByIdAsync ( userId ) ;
await EnablePremiumAsync ( user , expirationDate ) ;
2022-08-29 16:06:55 -04:00
}
2020-03-27 14:36:37 -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 ;
2020-03-27 14:36:37 -04:00
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
2022-08-29 14:53:16 -04:00
}
2022-08-29 16:06:55 -04:00
}
2017-08-16 17:08:20 -04:00
public async Task DisablePremiumAsync ( Guid userId , DateTime ? expirationDate )
2022-08-29 16:06:55 -04:00
{
2022-06-17 06:30:50 +10: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
}
2022-06-17 06:30:50 +10:00
public async Task DisablePremiumAsync ( User user , DateTime ? expirationDate )
2022-08-29 16:06:55 -04:00
{
2022-06-17 06:30:50 +10:00
if ( user ! = null & & user . Premium )
2022-08-29 14:53:16 -04:00
{
2022-06-17 06:30:50 +10: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
}
2022-06-17 06:30:50 +10:00
public async Task UpdatePremiumExpirationAsync ( Guid userId , DateTime ? expirationDate )
2022-08-29 16:06:55 -04:00
{
2022-06-17 06:30:50 +10:00
var user = await _userRepository . GetByIdAsync ( userId ) ;
2020-03-27 14:36:37 -04:00
if ( user ! = null )
2022-08-29 16:06:55 -04:00
{
2022-06-17 06:30:50 +10:00
user . PremiumExpirationDate = expirationDate ;
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
2018-08-28 16:23:58 -04:00
}
2022-08-29 16:06:55 -04:00
}
2022-06-17 06:30:50 +10:00
2024-12-05 09:31:14 -05:00
public async Task < UserLicense > GenerateLicenseAsync (
User user ,
SubscriptionInfo subscriptionInfo = null ,
2022-06-17 06:30:50 +10:00
int? version = null )
2022-08-29 16:06:55 -04:00
{
2019-08-10 12:59:32 -04:00
if ( user = = null )
2022-08-29 14:53:16 -04:00
{
2022-06-17 06:30:50 +10:00
throw new NotFoundException ( ) ;
2022-08-29 14:53:16 -04:00
}
2022-06-17 06:30:50 +10:00
2018-08-28 16:23:58 -04:00
if ( subscriptionInfo = = null & & user . Gateway ! = null )
2022-08-29 16:06:55 -04:00
{
2018-08-28 16:23:58 -04:00
subscriptionInfo = await _paymentService . GetSubscriptionAsync ( user ) ;
2022-08-29 16:06:55 -04:00
}
2022-06-17 06:30:50 +10:00
2024-12-05 09:31:14 -05:00
var userLicense = subscriptionInfo = = null
? new UserLicense ( user , _licenseService )
: new UserLicense ( user , subscriptionInfo , _licenseService ) ;
2025-01-03 15:34:29 -05:00
if ( _featureService . IsEnabled ( FeatureFlagKeys . SelfHostLicenseRefactor ) )
{
2025-01-03 16:00:52 -05:00
userLicense . Token = await _licenseService . CreateUserTokenAsync ( user , subscriptionInfo ) ;
2025-01-03 15:34:29 -05:00
}
2024-12-05 09:31:14 -05:00
return userLicense ;
2018-08-28 16:23:58 -04:00
}
2018-12-19 11:48:36 -05:00
public override async Task < bool > CheckPasswordAsync ( User user , string password )
2022-08-29 16:06:55 -04:00
{
2018-12-19 11:48:36 -05:00
if ( user = = null )
2022-08-29 16:06:55 -04:00
{
2018-12-19 11:48:36 -05:00
return false ;
2018-12-19 10:47:53 -05:00
}
2020-03-27 14:36:37 -04:00
var result = await base . VerifyPasswordAsync ( Store as IUserPasswordStore < User > , user , password ) ;
if ( result = = PasswordVerificationResult . SuccessRehashNeeded )
2022-08-29 14:53:16 -04:00
{
2020-03-27 14:36:37 -04:00
await UpdatePasswordHash ( user , password , false , false ) ;
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
2022-08-29 14:53:16 -04:00
}
2020-03-27 14:36:37 -04:00
var success = result ! = PasswordVerificationResult . Failed ;
if ( ! success )
2022-08-29 16:06:55 -04:00
{
2020-03-27 14:36:37 -04:00
Logger . LogWarning ( 0 , "Invalid password for user {userId}." , user . Id ) ;
2022-08-29 16:06:55 -04:00
}
2020-03-27 14:36:37 -04:00
return success ;
2022-08-29 16:06:55 -04:00
}
2020-03-27 14:36:37 -04:00
public async Task < bool > CanAccessPremium ( ITwoFactorProvidersUser user )
2022-08-29 15:53:48 -04:00
{
2020-03-27 14:36:37 -04:00
var userId = user . GetUserId ( ) ;
if ( ! userId . HasValue )
2018-12-19 10:47:53 -05:00
{
return false ;
}
2018-12-19 11:48:36 -05:00
return user . GetPremium ( ) | | await this . HasPremiumFromOrganization ( user ) ;
2022-08-29 16:06:55 -04:00
}
2018-12-19 11:48:36 -05:00
public async Task < bool > HasPremiumFromOrganization ( ITwoFactorProvidersUser user )
2022-08-29 16:06:55 -04:00
{
2018-12-19 11:48:36 -05:00
var userId = user . GetUserId ( ) ;
if ( ! userId . HasValue )
2018-12-19 10:47:53 -05:00
{
return false ;
}
2020-03-27 14:36:37 -04:00
// 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 ) ;
2018-12-19 10:47:53 -05:00
if ( ! orgUsers . Any ( ) )
2022-08-29 16:06:55 -04:00
{
2018-12-19 10:47:53 -05:00
return false ;
}
2018-12-19 11:48:36 -05:00
var orgAbilities = await _applicationCacheService . GetOrganizationAbilitiesAsync ( ) ;
2020-08-26 14:12:04 -04:00
return orgUsers . Any ( ou = >
orgAbilities . TryGetValue ( ou . OrganizationId , out var orgAbility ) & &
2018-12-19 10:47:53 -05:00
orgAbility . UsersGetPremium & &
2020-08-26 14:12:04 -04:00
orgAbility . Enabled ) ;
2022-08-29 16:06:55 -04:00
}
2020-08-26 14:12:04 -04:00
public async Task < bool > TwoFactorIsEnabledAsync ( ITwoFactorProvidersUser user )
2022-08-29 16:06:55 -04:00
{
2020-08-26 14:12:04 -04:00
var providers = user . GetTwoFactorProviders ( ) ;
2020-03-27 14:36:37 -04:00
if ( providers = = null )
2020-08-26 14:12:04 -04:00
{
return false ;
}
2021-11-09 16:37:32 +01:00
2018-04-17 08:10:17 -04:00
foreach ( var p in providers )
2015-12-08 22:57:38 -05:00
{
2020-03-27 14:36:37 -04:00
if ( p . Value ? . Enabled ? ? false )
2015-12-08 22:57:38 -05:00
{
2020-03-27 14:36:37 -04:00
if ( ! TwoFactorProvider . RequiresPremium ( p . Key ) )
2022-08-29 14:53:16 -04:00
{
2018-12-19 10:47:53 -05:00
return true ;
2022-08-29 16:06:55 -04:00
}
2020-03-27 14:36:37 -04:00
if ( await CanAccessPremium ( user ) )
2022-08-29 16:06:55 -04:00
{
2018-12-19 10:47:53 -05:00
return true ;
2015-12-08 22:57:38 -05:00
}
}
2022-08-29 16:06:55 -04:00
}
2018-12-19 10:47:53 -05: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 ) )
2020-02-28 09:14:33 -05:00
{
return true ;
2022-08-29 15:53:48 -04:00
}
2020-02-28 09:14:33 -05:00
return await CanAccessPremium ( user ) ;
}
2022-08-29 15:53:48 -04:00
2021-09-28 06:54:28 +10:00
public async Task < string > GenerateSignInTokenAsync ( User user , string purpose )
2022-08-29 16:06:55 -04:00
{
2021-09-28 06:54:28 +10:00
var token = await GenerateUserTokenAsync ( user , Options . Tokens . PasswordResetTokenProvider ,
2020-08-26 14:12:04 -04:00
purpose ) ;
2021-09-28 06:54:28 +10:00
return token ;
2022-08-29 14:53:16 -04:00
}
2020-02-28 09:14:33 -05:00
Auth/PM-3275 - Changes to support TDE User without MP being able to Set a Password + misc refactoring (#3242)
* PM-3275 - Add new GetMasterPasswordPolicy endpoint which will allow authenticated clients to get an enabled MP org policy if it exists for the purposes of enforcing those policy requirements when setting a password.
* PM-3275 - AccountsController.cs - PostSetPasswordAsync - (1) Convert UserService.setPasswordAsync into new SetInitialMasterPasswordCommand (2) Refactor SetInitialMasterPasswordCommand to only accept post SSO users who are in the invited state
(3) Add TODOs for more cleanup work and more commands
* PM-3275 - Update AccountsControllerTests.cs to add new SetInitialMasterPasswordCommand
* PM-3275 - UserService.cs - Remove non implemented ChangePasswordAsync method
* PM-3275 - The new SetInitialMasterPasswordCommand leveraged the OrganizationService.cs AcceptUserAsync method so while I was in here I converted the AcceptUserAsync methods into a new AcceptOrgUserCommand.cs and turned the private method which accepted an existing org user public for use in the SetInitialMasterPasswordCommand
* PM-3275 - Dotnet format
* PM-3275 - Test SetInitialMasterPasswordCommand
* Dotnet format
* PM-3275 - In process AcceptOrgUserCommandTests.cs
* PM-3275 - Migrate changes from AC-244 / #3199 over into new AcceptOrgUserCommand
* PM-3275 - AcceptOrgUserCommand.cs - create data protector specifically for this command
* PM-3275 - Add TODO for renaming / removing overloading of methods to improve readability / clarity
* PM-3275 - AcceptOrgUserCommand.cs - refactor AcceptOrgUserAsync by OrgId to retrieve orgUser with _organizationUserRepository.GetByOrganizationAsync which gets a single user instead of a collection
* PM-3275 - AcceptOrgUserCommand.cs - update name in TODO for evaluation later
* PM-3275 / PM-1196 - (1) Slightly refactor SsoEmail2faSessionTokenable to provide public static GetTokenLifeTime() method for testing (2) Add missed tests to SsoEmail2faSessionTokenable in preparation for building tests for new OrgUserInviteTokenable.cs
* PM-3275 / PM-1196 - Removing SsoEmail2faSessionTokenable.cs changes + tests as I've handled that separately in a new PR (#3270) for newly created task PM-3925
* PM-3275 - ExpiringTokenable.cs - add clarifying comments to help distinguish between the Valid property and the TokenIsValid method.
* PM-3275 - Create OrgUserInviteTokenable.cs and add tests in OrgUserInviteTokenableTests.cs
* PM-3275 - OrganizationService.cs - Refactor Org User Invite methods to use new OrgUserInviteTokenable instead of manual creation of a token
* PM-3275 - OrgUserInviteTokenable.cs - clarify backwards compat note
* PM-3275 - AcceptOrgUserCommand.cs - Add TODOs + minor name refactor
* PM-3275 - AcceptOrgUserCommand.cs - replace method overloading with more easily readable names.
* PM-3275 - AcceptOrgUserCommand.cs - Update ValidateOrgUserInviteToken to add new token validation while maintaining backwards compatibility for 1 release.
* dotnet format
* PM-3275 - AcceptOrgUserCommand.cs - Move private method below where it is used
* PM-3275 - ServiceCollectionExtensions.cs - Must register IDataProtectorTokenFactory<OrgUserInviteTokenable> for new tokenable
* PM-3275 - OrgUserInviteTokenable needed access to global settings to set its token lifetime to the _globalSettings.OrganizationInviteExpirationHours value. Creating a factory seemed the most straightforward way to encapsulate the desired creation logic. Unsure if in the correct location in ServiceCollectionExtensions.cs but will figure that out later.
* PM-3275 - In process work of creating AcceptOrgUserCommandTests.cs
* PM-3275 - Remove no longer relevant AcceptOrgUser tests from OrganizationServiceTests.cs
* PM-3275 - Register OrgUserInviteTokenableFactory alongside tokenizer
* PM-3275 - AcceptOrgUserCommandTests.cs - AcceptOrgUserAsync basic test suite completed.
* PM-3275 - AcceptOrgUserCommandTests.cs - tweak test names
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Remove old tests from OrganizationServiceTests as no longer needed to reference (2) Add summary for SetupCommonAcceptOrgUserMocks (3) Get AcceptOrgUserByToken_OldToken_AcceptsUserAndVerifiesEmail passing
* PM-3275 - Create interface for OrgUserInviteTokenableFactory b/c that's the right thing to do + enables test substitution
* PM-3275 - AcceptOrgUserCommandTests.cs - (1) Start work on AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail (2) Create and use SetupCommonAcceptOrgUserByTokenMocks() (3) Create generic FakeDataProtectorTokenFactory for tokenable testing
* PM-3275 - (1) Get AcceptOrgUserByToken_NewToken_AcceptsUserAndVerifiesEmail test passing (2) Move FakeDataProtectorTokenFactory to own file
* PM-3275 - AcceptOrgUserCommandTests.cs - Finish up tests for AcceptOrgUserByTokenAsync
* PM-3275 - Add pseudo section comments
* PM-3275 - Clean up unused params on AcceptOrgUserByToken_EmailMismatch_ThrowsBadRequest test
* PM-3275 - (1) Tests written for AcceptOrgUserByOrgSsoIdAsync (2) Refactor happy path assertions into helper function AssertValidAcceptedOrgUser to reduce code duplication
* PM-3275 - Finish up testing AcceptOrgUserCommandTests.cs by adding tests for AcceptOrgUserByOrgIdAsync
* PM-3275 - Tweaking test naming to ensure consistency.
* PM-3275 - Bugfix - OrgUserInviteTokenableFactory implementation required when declaring singleton service in ServiceCollectionExtensions.cs
* PM-3275 - Resolve failing OrganizationServiceTests.cs
* dotnet format
* PM-3275 - PoliciesController.cs - GetMasterPasswordPolicy bugfix - for orgs without a MP policy, policy comes back as null and we should return notFound in that case.
* PM-3275 - Add PoliciesControllerTests.cs specifically for new GetMasterPasswordPolicy(...) endpoint.
* PM-3275 - dotnet format PoliciesControllerTests.cs
* PM-3275 - PoliciesController.cs - (1) Add tech debt task number (2) Properly flag endpoint as deprecated
* PM-3275 - Add new hasManageResetPasswordPermission property to ProfileResponseModel.cs primarily for sync so that we can condition client side if TDE user obtains elevated permissions
* PM-3275 - Fix AccountsControllerTests.cs
* PM-3275 - OrgUserInviteTokenable.cs - clarify TODO
* PM-3275 - AcceptOrgUserCommand.cs - Refactor token validation to use short circuiting to only run old token validation if new token validation fails.
* PM-3275 - OrgUserInviteTokenable.cs - (1) Add new static methods to centralize validation logic to avoid repetition (2) Add new token validation method so we can avoid having to pass in a full org user (and hitting the db to do so)
* PM-3275 - Realized that the old token validation was used in the PoliciesController.cs (existing user clicks invite link in email and goes to log in) and UserService.cs (user clicks invite link in email and registers for a new acct). Added tech debt item for cleaning up backwards compatibility in future.
* dotnet format
* PM-3275 - (1) AccountsController.cs - Update PostSetPasswordAsync SetPasswordRequestModel to allow null keys for the case where we have a TDE user who obtains elevated permissions - they already have a user public and user encrypted private key saved in the db. (2) AccountsControllerTests.cs - test PostSetPasswordAsync scenarios to ensure changes will work as expected.
* PM-3275 - PR review feedback - (1) set CurrentContext to private (2) Refactor GetProfile to use variables to improve clarity and simplify debugging.
* PM-3275 - SyncController.cs - PR Review Feedback - Set current context as private instead of protected.
* PM-3275 - CurrentContextExtensions.cs - PR Feedback - move parenthesis up from own line.
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - Replace unnecessary variable
* PM-3275 - SetInitialMasterPasswordCommandTests.cs - PR Feedback - Add expected outcome statement to test name
* PM-3275 - Set Initial Password command and tests - PR Feedback changes - (1) Rename orgIdentifier --> OrgSsoIdentifier for clarity (2) Update SetInitialMasterPasswordAsync to not allow null orgSsoId with explicit message saying this vs letting null org trigger invalid organization (3) Add test to cover this new scenario.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Move summary from implementation to interface to better respect standards and the fact that the interface is the more seen piece of code.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, rename AcceptOrgUserByTokenAsync -> AcceptOrgUserByEmailTokenAsync + replace generic name token with emailToken
* PM-3275 - OrganizationService.cs - Per PR feedback, remove dupe line
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove new lines in error messages for consistency.
* PM-3275 - SetInitialMasterPasswordCommand.cs - Per PR feedback, adjust formatting of constructor for improved readability.
* PM-3275 - CurrentContextExtensions.cs - Refactor AnyOrgUserHasManageResetPasswordPermission per PR feedback to remove unnecessary var.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, remove completed TODO
* PM-3275 - PoliciesController.cs - Per PR feedback, update GetByInvitedUser param to be guid instead of string.
* PM-3275 - OrgUserInviteTokenable.cs - per PR feedback, add tech debt item info.
* PM-3275 - AcceptOrgUserCommand.cs - Per PR feedback, use const purpose from tokenable instead of magic string.
* PM-3275 - Restore non duplicate line to fix tests
* PM-3275 - Per PR feedback, revert all sync controller changes as the ProfileResponseModel.organizations array has org objects which have permissions which have the ManageResetPassword permission. So, I have the information that I need clientside already to determine if the user has the ManageResetPassword in any org.
* PM-3275 - PoliciesControllerTests.cs - Update imports as the PoliciesController was moved under the admin console team's domain.
* PM-3275 - Resolve issues from merge conflict resolutions to get solution building.
* PM-3275 / PM-4633 - PoliciesController.cs - use orgUserId to look up user instead of orgId. Oops.
* Fix user service tests
* Resolve merge conflict
2023-11-02 11:02:25 -04:00
public async Task < IdentityResult > UpdatePasswordHash ( User user , string newPassword ,
2020-02-28 09:14:33 -05:00
bool validatePassword = true , bool refreshStamp = true )
2022-08-29 16:06:55 -04:00
{
2020-02-28 09:14:33 -05:00
if ( validatePassword )
2022-08-29 14:53:16 -04:00
{
2020-02-28 09:14:33 -05:00
var validate = await ValidatePasswordInternal ( user , newPassword ) ;
if ( ! validate . Succeeded )
2022-08-29 15:53:48 -04:00
{
2020-02-28 09:14:33 -05:00
return validate ;
2022-08-29 14:53:16 -04:00
}
2022-08-29 16:06:55 -04:00
}
2020-02-28 09:14:33 -05:00
2017-06-19 22:08:10 -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
{
2017-06-19 22:08:10 -04:00
user . SecurityStamp = Guid . NewGuid ( ) . ToString ( ) ;
2022-08-29 16:06:55 -04:00
}
2022-08-29 14:53:16 -04:00
2020-03-27 14:36:37 -04:00
return IdentityResult . Success ;
2022-08-29 16:06:55 -04:00
}
2024-06-03 09:19:56 -04:00
public async Task < bool > IsLegacyUser ( string userId )
{
if ( string . IsNullOrWhiteSpace ( userId ) )
{
return false ;
}
var user = await FindByIdAsync ( userId ) ;
if ( user = = null )
{
return false ;
}
return IsLegacyUser ( user ) ;
}
2024-09-11 11:29:57 +01:00
public async Task < bool > IsManagedByAnyOrganizationAsync ( Guid userId )
2024-09-26 11:21:51 +01:00
{
2024-10-17 16:06:32 +01:00
var managingOrganizations = await GetOrganizationsManagingUserAsync ( userId ) ;
return managingOrganizations . Any ( ) ;
2024-09-26 11:21:51 +01:00
}
2024-10-17 16:06:32 +01:00
public async Task < IEnumerable < Organization > > GetOrganizationsManagingUserAsync ( Guid userId )
2024-09-11 11:29:57 +01:00
{
2024-10-17 16:06:32 +01:00
if ( ! _featureService . IsEnabled ( FeatureFlagKeys . AccountDeprovisioning ) )
{
return Enumerable . Empty < Organization > ( ) ;
}
// Get all organizations that have verified the user's email domain.
var organizationsWithVerifiedUserEmailDomain = await _organizationRepository . GetByVerifiedUserEmailDomainAsync ( userId ) ;
2024-09-11 11:29:57 +01:00
2024-10-17 16:06:32 +01:00
// Organizations must be enabled and able to have verified domains.
2024-09-11 11:29:57 +01:00
// TODO: Replace "UseSso" with a new organization ability like "UseOrganizationDomains" (PM-11622).
// Verified domains were tied to SSO, so we currently check the "UseSso" organization ability.
2024-10-17 16:06:32 +01:00
return organizationsWithVerifiedUserEmailDomain . Where ( organization = > organization is { Enabled : true , UseSso : true } ) ;
2024-09-11 11:29:57 +01:00
}
2024-06-03 09:19:56 -04:00
/// <inheritdoc cref="IsLegacyUser(string)"/>
public static bool IsLegacyUser ( User user )
{
return user . Key = = null & & user . MasterPassword ! = null & & user . PrivateKey ! = null ;
}
2020-03-27 14:36:37 -04:00
private async Task < IdentityResult > ValidatePasswordInternal ( User user , string password )
2022-08-29 16:06:55 -04:00
{
2020-03-27 14:36:37 -04:00
var errors = new List < IdentityError > ( ) ;
foreach ( var v in _passwordValidators )
2022-08-29 16:06:55 -04:00
{
2020-03-27 14:36:37 -04:00
var result = await v . ValidateAsync ( this , user , password ) ;
if ( ! result . Succeeded )
2020-02-28 09:14:33 -05:00
{
errors . AddRange ( result . Errors ) ;
}
}
2020-02-28 10:23:19 -05:00
if ( errors . Count > 0 )
{
2021-09-28 06:54:28 +10:00
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
}
2021-09-28 06:54:28 +10:00
return IdentityResult . Success ;
}
public void SetTwoFactorProvider ( User user , TwoFactorProviderType type , bool setEnabled = true )
2022-08-29 16:06:55 -04:00
{
2021-09-28 06:54:28 +10:00
var providers = user . GetTwoFactorProviders ( ) ;
if ( ! providers ? . ContainsKey ( type ) ? ? true )
2022-08-29 16:06:55 -04:00
{
2021-09-28 06:54:28 +10:00
return ;
2020-02-28 10:23:19 -05:00
}
2020-07-15 12:38:45 -04:00
if ( setEnabled )
{
providers [ type ] . Enabled = true ;
}
2021-11-08 15:55:42 -05:00
user . SetTwoFactorProviders ( providers ) ;
2020-11-10 15:15:29 -05:00
if ( string . IsNullOrWhiteSpace ( user . TwoFactorRecoveryCode ) )
{
user . TwoFactorRecoveryCode = CoreHelpers . SecureRandomString ( 32 , upper : false , special : false ) ;
}
2022-08-29 16:06:55 -04:00
}
2021-11-09 16:37:32 +01:00
2024-10-16 10:33:00 +01:00
private async Task CheckPoliciesOnTwoFactorRemovalAsync ( User user )
2021-11-09 16:37:32 +01:00
{
2023-05-12 08:22:19 +01:00
var twoFactorPolicies = await _policyService . GetPoliciesApplicableToUserAsync ( user . Id , PolicyType . TwoFactorAuthentication ) ;
2024-12-13 11:32:29 +00:00
var organizationsManagingUser = await GetOrganizationsManagingUserAsync ( user . Id ) ;
2021-11-09 16:37:32 +01:00
var removeOrgUserTasks = twoFactorPolicies . Select ( async p = >
2022-08-29 15:53:48 -04:00
{
2021-11-09 16:37:32 +01:00
var organization = await _organizationRepository . GetByIdAsync ( p . OrganizationId ) ;
2024-12-13 11:32:29 +00:00
if ( _featureService . IsEnabled ( FeatureFlagKeys . AccountDeprovisioning ) & & organizationsManagingUser . Any ( o = > o . Id = = p . OrganizationId ) )
{
await _revokeNonCompliantOrganizationUserCommand . RevokeNonCompliantOrganizationUsersAsync (
new RevokeOrganizationUsersRequest (
p . OrganizationId ,
[new OrganizationUserUserDetails { UserId = user.Id, OrganizationId = p.OrganizationId }] ,
new SystemUser ( EventSystemUser . TwoFactorDisabled ) ) ) ;
await _mailService . SendOrganizationUserRevokedForTwoFactoryPolicyEmailAsync ( organization . DisplayName ( ) , user . Email ) ;
}
else
{
await _removeOrganizationUserCommand . RemoveUserAsync ( p . OrganizationId , user . Id ) ;
await _mailService . SendOrganizationUserRemovedForPolicyTwoStepEmailAsync (
organization . DisplayName ( ) , user . Email ) ;
}
2021-11-09 16:37:32 +01:00
} ) . ToArray ( ) ;
await Task . WhenAll ( removeOrgUserTasks ) ;
}
2022-08-29 14:53:16 -04:00
2021-11-09 16:37:32 +01:00
public override async Task < IdentityResult > ConfirmEmailAsync ( User user , string token )
2022-08-29 16:06:55 -04:00
{
2021-11-09 16:37:32 +01:00
var result = await base . ConfirmEmailAsync ( user , token ) ;
if ( result . Succeeded )
{
await _referenceEventService . RaiseEventAsync (
2023-05-16 16:21:57 +02:00
new ReferenceEvent ( ReferenceEventType . ConfirmEmailAddress , user , _currentContext ) ) ;
2021-11-09 16:37:32 +01:00
}
2020-07-15 12:38:45 -04:00
return result ;
2022-08-29 16:06:55 -04:00
}
2020-11-10 15:15:29 -05: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 ) ;
2020-11-10 15:15:29 -05:00
user . RevisionDate = DateTime . UtcNow ;
await _userRepository . ReplaceAsync ( user ) ;
2022-08-29 16:06:55 -04:00
}
2022-04-28 13:14:09 -03:00
public async Task SendOTPAsync ( User user )
2022-08-29 16:06:55 -04:00
{
2024-12-16 07:57:56 -08:00
if ( string . IsNullOrEmpty ( user . Email ) )
2022-04-28 13:14:09 -03:00
{
throw new BadRequestException ( "No user email." ) ;
}
2021-11-09 16:37:32 +01:00
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
}
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
{
2023-08-17 16:03:06 -04:00
return await base . VerifyUserTokenAsync ( user , TokenOptions . DefaultEmailProvider ,
2021-11-09 16:37:32 +01:00
"otp:" + user . Email , token ) ;
2022-08-29 16:06:55 -04:00
}
2024-07-22 11:21:14 -04:00
public async Task < bool > VerifySecretAsync ( User user , string secret , bool isSettingMFA = false )
2022-08-29 16:06:55 -04:00
{
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 ) ;
}
2024-07-22 11:21:14 -04:00
else if ( isSettingMFA )
{
// this is temporary to allow users to view their MFA settings without invalidating email TOTP
// Will be removed with PM-9925
isVerified = true ;
}
2023-08-17 16:03:06 -04:00
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
}
2024-03-11 14:36:32 +01:00
2024-12-16 07:57:56 -08:00
public async Task ResendNewDeviceVerificationEmail ( string email , string secret )
{
var user = await _userRepository . GetByEmailAsync ( email ) ;
if ( user = = null )
{
return ;
}
if ( await VerifySecretAsync ( user , secret ) )
{
await SendOTPAsync ( user ) ;
}
}
2025-01-09 18:10:54 -08:00
public async Task < bool > ActiveNewDeviceVerificationException ( Guid userId )
{
var cacheKey = string . Format ( AuthConstants . NewDeviceVerificationExceptionCacheKeyFormat , userId . ToString ( ) ) ;
var cacheValue = await _distributedCache . GetAsync ( cacheKey ) ;
return cacheValue ! = null ;
}
public async Task ToggleNewDeviceVerificationException ( Guid userId )
{
var cacheKey = string . Format ( AuthConstants . NewDeviceVerificationExceptionCacheKeyFormat , userId . ToString ( ) ) ;
var cacheValue = await _distributedCache . GetAsync ( cacheKey ) ;
if ( cacheValue ! = null )
{
await _distributedCache . RemoveAsync ( cacheKey ) ;
}
else
{
await _distributedCache . SetAsync ( cacheKey , new byte [ 1 ] , new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan . FromHours ( 24 )
} ) ;
}
}
2024-03-11 14:36:32 +01:00
private async Task SendAppropriateWelcomeEmailAsync ( User user , string initiationPath )
{
var isFromMarketingWebsite = initiationPath . Contains ( "Secrets Manager trial" ) ;
if ( isFromMarketingWebsite )
{
await _mailService . SendTrialInitiationEmailAsync ( user . Email ) ;
}
else
{
await _mailService . SendWelcomeEmailAsync ( user ) ;
}
}
2015-12-08 22:57:38 -05:00
}