From c5d5601464d8aaab1374f3d54f06e0e52b8ec553 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Mon, 25 Oct 2021 15:09:14 +0200 Subject: [PATCH] Add support for crypto agent (#1623) --- .../src/Sso/Controllers/AccountController.cs | 7 +- .../DynamicAuthenticationSchemeProvider.cs | 7 +- src/Api/Controllers/AccountsController.cs | 23 ++ .../CustomTokenRequestValidator.cs | 29 ++- .../Accounts/SetCryptoAgentKeyRequestModel.cs | 29 +++ .../OrganizationSsoRequestModel.cs | 13 +- .../Response/OrganizationSsoResponseModel.cs | 5 +- src/Core/Models/Data/SsoConfigurationData.cs | 4 + src/Core/Models/Table/SsoConfig.cs | 19 +- src/Core/Models/Table/User.cs | 1 + src/Core/Services/IUserService.cs | 1 + .../Services/Implementations/UserService.cs | 27 ++ src/Identity/Controllers/AccountController.cs | 4 + src/Sql/dbo/Stored Procedures/User_Create.sql | 9 +- src/Sql/dbo/Stored Procedures/User_Update.sql | 6 +- src/Sql/dbo/Tables/User.sql | 1 + .../AutoFixture/SsoConfigFixtures.cs | 4 +- .../DbScripts/2021-10-13_00_CryptoAgent.sql | 239 ++++++++++++++++++ 18 files changed, 397 insertions(+), 31 deletions(-) create mode 100644 src/Core/Models/Api/Request/Accounts/SetCryptoAgentKeyRequestModel.cs create mode 100644 util/Migrator/DbScripts/2021-10-13_00_CryptoAgent.sql diff --git a/bitwarden_license/src/Sso/Controllers/AccountController.cs b/bitwarden_license/src/Sso/Controllers/AccountController.cs index 702502ec58..4fc77bd4c1 100644 --- a/bitwarden_license/src/Sso/Controllers/AccountController.cs +++ b/bitwarden_license/src/Sso/Controllers/AccountController.cs @@ -328,12 +328,7 @@ namespace Bit.Sso.Controllers throw new Exception(_i18nService.T("OrganizationOrSsoConfigNotFound")); } - var options = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }; - var ssoConfigData = JsonSerializer.Deserialize(ssoConfig.Data, options); - + var ssoConfigData = ssoConfig.GetData(); var externalUser = result.Principal; // Validate acr claim against expectation before going further diff --git a/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs b/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs index 7b457d029c..7220e12a93 100644 --- a/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs +++ b/bitwarden_license/src/Sso/Utilities/DynamicAuthenticationSchemeProvider.cs @@ -270,12 +270,7 @@ namespace Bit.Core.Business.Sso private DynamicAuthenticationScheme GetSchemeFromSsoConfig(SsoConfig config) { - var options = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }; - var data = JsonSerializer.Deserialize(config.Data, options); - + var data = config.GetData(); return data.ConfigType switch { SsoType.OpenIdConnect => GetOidcAuthenticationScheme(config.OrganizationId.ToString(), data), diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index 4891b5f1ae..e2dc8b20c4 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -256,6 +256,29 @@ namespace Bit.Api.Controllers throw new BadRequestException(ModelState); } + [HttpPost("set-crypto-agent-key")] + public async Task PostSetCryptoAgentKeyAsync([FromBody]SetCryptoAgentKeyRequestModel model) + { + var user = await _userService.GetUserByPrincipalAsync(User); + if (user == null) + { + throw new UnauthorizedAccessException(); + } + + var result = await _userService.SetCryptoAgentKeyAsync(model.ToUser(user), model.Key, model.OrgIdentifier); + if (result.Succeeded) + { + return; + } + + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + + throw new BadRequestException(ModelState); + } + [HttpPost("kdf")] public async Task PostKdf([FromBody]KdfRequestModel model) { diff --git a/src/Core/IdentityServer/CustomTokenRequestValidator.cs b/src/Core/IdentityServer/CustomTokenRequestValidator.cs index 501f971669..73af2070fe 100644 --- a/src/Core/IdentityServer/CustomTokenRequestValidator.cs +++ b/src/Core/IdentityServer/CustomTokenRequestValidator.cs @@ -1,4 +1,5 @@ -using Bit.Core.Models.Table; +using System; +using Bit.Core.Models.Table; using Bit.Core.Repositories; using IdentityServer4.Validation; using Microsoft.AspNetCore.Identity; @@ -9,7 +10,9 @@ using Bit.Core.Services; using Bit.Core.Settings; using Bit.Core.Context; using System.Linq; +using System.Text.Json; using Bit.Core.Identity; +using Bit.Core.Models.Data; using Microsoft.Extensions.Logging; using IdentityServer4.Extensions; using IdentityModel; @@ -20,6 +23,7 @@ namespace Bit.Core.IdentityServer ICustomTokenRequestValidator { private UserManager _userManager; + private readonly ISsoConfigRepository _ssoConfigRepository; public CustomTokenRequestValidator( UserManager userManager, @@ -35,12 +39,14 @@ namespace Bit.Core.IdentityServer ILogger logger, ICurrentContext currentContext, GlobalSettings globalSettings, - IPolicyRepository policyRepository) + IPolicyRepository policyRepository, + ISsoConfigRepository ssoConfigRepository) : base(userManager, deviceRepository, deviceService, userService, eventService, organizationDuoWebTokenProvider, organizationRepository, organizationUserRepository, applicationCacheService, mailService, logger, currentContext, globalSettings, policyRepository) { _userManager = userManager; + _ssoConfigRepository = ssoConfigRepository; } public async Task ValidateAsync(CustomTokenRequestValidationContext context) @@ -52,6 +58,25 @@ namespace Bit.Core.IdentityServer return; } await ValidateAsync(context, context.Result.ValidatedRequest); + + if (context.Result.CustomResponse != null) + { + var organizationClaim = context.Result.ValidatedRequest.Subject?.FindFirst(c => c.Type == "organizationId"); + var organizationId = organizationClaim?.Value ?? ""; + + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(new Guid(organizationId)); + var ssoConfigData = ssoConfig.GetData(); + + if (ssoConfigData is { UseCryptoAgent: true } && !string.IsNullOrEmpty(ssoConfigData.CryptoAgentUrl)) + { + context.Result.CustomResponse["CryptoAgentUrl"] = ssoConfigData.CryptoAgentUrl; + // Prevent clients redirecting to set-password + // TODO: Figure out if we can move this logic to the clients since this might break older clients + // although we will have issues either way with some clients supporting crypto anent and some not + // suggestion: We should roll out the clients before enabling it server wise + context.Result.CustomResponse["ResetMasterPassword"] = false; + } + } } protected async override Task<(User, bool)> ValidateContextAsync(CustomTokenRequestValidationContext context) diff --git a/src/Core/Models/Api/Request/Accounts/SetCryptoAgentKeyRequestModel.cs b/src/Core/Models/Api/Request/Accounts/SetCryptoAgentKeyRequestModel.cs new file mode 100644 index 0000000000..01041db3db --- /dev/null +++ b/src/Core/Models/Api/Request/Accounts/SetCryptoAgentKeyRequestModel.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.DataAnnotations; +using Bit.Core.Enums; +using Bit.Core.Models.Table; + +namespace Bit.Core.Models.Api.Request.Accounts +{ + public class SetCryptoAgentKeyRequestModel + { + [Required] + public string Key { get; set; } + [Required] + public KeysRequestModel Keys { get; set; } + [Required] + public KdfType Kdf { get; set; } + [Required] + public int KdfIterations { get; set; } + [Required] + public string OrgIdentifier { get; set; } + + public User ToUser(User existingUser) + { + existingUser.Kdf = Kdf; + existingUser.KdfIterations = KdfIterations; + existingUser.Key = Key; + Keys.ToUser(existingUser); + return existingUser; + } + } +} diff --git a/src/Core/Models/Api/Request/Organizations/OrganizationSsoRequestModel.cs b/src/Core/Models/Api/Request/Organizations/OrganizationSsoRequestModel.cs index f547f367e4..e08b19004e 100644 --- a/src/Core/Models/Api/Request/Organizations/OrganizationSsoRequestModel.cs +++ b/src/Core/Models/Api/Request/Organizations/OrganizationSsoRequestModel.cs @@ -31,10 +31,7 @@ namespace Bit.Core.Models.Api { existingConfig.Enabled = Enabled; var configurationData = Data.ToConfigurationData(); - existingConfig.Data = JsonSerializer.Serialize(configurationData, new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }); + existingConfig.SetData(configurationData); return existingConfig; } } @@ -46,6 +43,8 @@ namespace Bit.Core.Models.Api public SsoConfigurationDataRequest(SsoConfigurationData configurationData) { ConfigType = configurationData.ConfigType; + UseCryptoAgent = configurationData.UseCryptoAgent; + CryptoAgentUrl = configurationData.CryptoAgentUrl; Authority = configurationData.Authority; ClientId = configurationData.ClientId; ClientSecret = configurationData.ClientSecret; @@ -79,6 +78,10 @@ namespace Bit.Core.Models.Api [Required] public SsoType ConfigType { get; set; } + // Crypto Agent + public bool UseCryptoAgent { get; set; } + public string CryptoAgentUrl { get; set; } + // OIDC public string Authority { get; set; } public string ClientId { get; set; } @@ -193,6 +196,8 @@ namespace Bit.Core.Models.Api return new SsoConfigurationData { ConfigType = ConfigType, + UseCryptoAgent = UseCryptoAgent, + CryptoAgentUrl = CryptoAgentUrl, Authority = Authority, ClientId = ClientId, ClientSecret = ClientSecret, diff --git a/src/Core/Models/Api/Response/OrganizationSsoResponseModel.cs b/src/Core/Models/Api/Response/OrganizationSsoResponseModel.cs index 9cad8649cc..6132e611a4 100644 --- a/src/Core/Models/Api/Response/OrganizationSsoResponseModel.cs +++ b/src/Core/Models/Api/Response/OrganizationSsoResponseModel.cs @@ -13,10 +13,7 @@ namespace Bit.Core.Models.Api if (config != null) { Enabled = config.Enabled; - Data = JsonSerializer.Deserialize(config.Data, new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }); + Data = config.GetData(); } else { diff --git a/src/Core/Models/Data/SsoConfigurationData.cs b/src/Core/Models/Data/SsoConfigurationData.cs index b7b20ebfb5..1434ad530a 100644 --- a/src/Core/Models/Data/SsoConfigurationData.cs +++ b/src/Core/Models/Data/SsoConfigurationData.cs @@ -15,6 +15,10 @@ namespace Bit.Core.Models.Data public SsoType ConfigType { get; set; } + // Crypto Agent + public bool UseCryptoAgent { get; set; } + public string CryptoAgentUrl { get; set; } + // OIDC public string Authority { get; set; } public string ClientId { get; set; } diff --git a/src/Core/Models/Table/SsoConfig.cs b/src/Core/Models/Table/SsoConfig.cs index 6a1b232126..6a63f22034 100644 --- a/src/Core/Models/Table/SsoConfig.cs +++ b/src/Core/Models/Table/SsoConfig.cs @@ -1,4 +1,6 @@ using System; +using System.Text.Json; +using Bit.Core.Models.Data; namespace Bit.Core.Models.Table { @@ -10,11 +12,26 @@ namespace Bit.Core.Models.Table public string Data { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; - + + private JsonSerializerOptions _jsonSerializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + public void SetNewId() { // int will be auto-populated Id = 0; } + + public SsoConfigurationData GetData() + { + return JsonSerializer.Deserialize(Data, _jsonSerializerOptions); + } + + public void SetData(SsoConfigurationData data) + { + Data = JsonSerializer.Serialize(data, _jsonSerializerOptions); + } } } diff --git a/src/Core/Models/Table/User.cs b/src/Core/Models/Table/User.cs index 61c5b4eef5..449b911ce6 100644 --- a/src/Core/Models/Table/User.cs +++ b/src/Core/Models/Table/User.cs @@ -58,6 +58,7 @@ namespace Bit.Core.Models.Table public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; public bool ForcePasswordReset { get; set; } + public bool UsesCryptoAgent { get; set; } public void SetNewId() { diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index f5de4e2d45..256575b1ce 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -34,6 +34,7 @@ namespace Bit.Core.Services string token, string key); Task ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, string key); Task SetPasswordAsync(User user, string newMasterPassword, string key, string orgIdentifier = null); + Task SetCryptoAgentKeyAsync(User user, string key, string orgIdentifier); Task AdminResetPasswordAsync(OrganizationUserType type, Guid orgId, Guid id, string newMasterPassword, string key); Task UpdateTempPasswordAsync(User user, string newMasterPassword, string key, string hint); Task ChangeKdfAsync(User user, string masterPassword, string newMasterPassword, string key, diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index a550cde506..70ade91cfc 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -635,6 +635,33 @@ namespace Bit.Core.Services return IdentityResult.Success; } + + public async Task SetCryptoAgentKeyAsync(User user, string key, string orgIdentifier) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (user.UsesCryptoAgent) + { + Logger.LogWarning("Already uses crypto agent."); + return IdentityResult.Failed(_identityErrorDescriber.UserAlreadyHasPassword()); + } + + user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow; + user.Key = key; + user.UsesCryptoAgent = true; + + await _userRepository.ReplaceAsync(user); + // TODO: Use correct event + await _eventService.LogUserEventAsync(user.Id, EventType.User_ChangedPassword); + + await _organizationService.AcceptUserAsync(orgIdentifier, user, this); + + return IdentityResult.Success; + } + public async Task AdminResetPasswordAsync(OrganizationUserType callingUserType, Guid orgId, Guid id, string newMasterPassword, string key) { diff --git a/src/Identity/Controllers/AccountController.cs b/src/Identity/Controllers/AccountController.cs index 9e4e01b6f6..5018ba2275 100644 --- a/src/Identity/Controllers/AccountController.cs +++ b/src/Identity/Controllers/AccountController.cs @@ -179,6 +179,10 @@ namespace Bit.Identity.Controllers IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(1) }; + if (result.Properties != null && result.Properties.Items.TryGetValue("domain_hint", out var organization)) + { + additionalLocalClaims.Add(new Claim("organizationId", organization)); + } ProcessLoginCallback(result, additionalLocalClaims, localSignInProps); // Issue authentication cookie for user diff --git a/src/Sql/dbo/Stored Procedures/User_Create.sql b/src/Sql/dbo/Stored Procedures/User_Create.sql index 09767e4b1c..882a632834 100644 --- a/src/Sql/dbo/Stored Procedures/User_Create.sql +++ b/src/Sql/dbo/Stored Procedures/User_Create.sql @@ -30,7 +30,8 @@ @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), @ApiKey VARCHAR(30), - @ForcePasswordReset BIT = 0 + @ForcePasswordReset BIT = 0, + @UsesCryptoAgent BIT = 0 AS BEGIN SET NOCOUNT ON @@ -68,7 +69,8 @@ BEGIN [CreationDate], [RevisionDate], [ApiKey], - [ForcePasswordReset] + [ForcePasswordReset], + [UsesCryptoAgent] ) VALUES ( @@ -103,6 +105,7 @@ BEGIN @CreationDate, @RevisionDate, @ApiKey, - @ForcePasswordReset + @ForcePasswordReset, + @UsesCryptoAgent ) END diff --git a/src/Sql/dbo/Stored Procedures/User_Update.sql b/src/Sql/dbo/Stored Procedures/User_Update.sql index e8eb5f2099..d851ce433b 100644 --- a/src/Sql/dbo/Stored Procedures/User_Update.sql +++ b/src/Sql/dbo/Stored Procedures/User_Update.sql @@ -30,7 +30,8 @@ @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7), @ApiKey VARCHAR(30), - @ForcePasswordReset BIT = 0 + @ForcePasswordReset BIT = 0, + @UsesCryptoAgent BIT = 0 AS BEGIN SET NOCOUNT ON @@ -68,7 +69,8 @@ BEGIN [CreationDate] = @CreationDate, [RevisionDate] = @RevisionDate, [ApiKey] = @ApiKey, - [ForcePasswordReset] = @ForcePasswordReset + [ForcePasswordReset] = @ForcePasswordReset, + [UsesCryptoAgent] = @UsesCryptoAgent WHERE [Id] = @Id END diff --git a/src/Sql/dbo/Tables/User.sql b/src/Sql/dbo/Tables/User.sql index 7abfd73e1f..b6c9a6dc2f 100644 --- a/src/Sql/dbo/Tables/User.sql +++ b/src/Sql/dbo/Tables/User.sql @@ -31,6 +31,7 @@ [RevisionDate] DATETIME2 (7) NOT NULL, [ApiKey] VARCHAR (30) NOT NULL, [ForcePasswordReset] BIT NOT NULL, + [UsesCryptoAgent] BIT NOT NULL, CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC) ); diff --git a/test/Core.Test/AutoFixture/SsoConfigFixtures.cs b/test/Core.Test/AutoFixture/SsoConfigFixtures.cs index c41f31be40..14e681af75 100644 --- a/test/Core.Test/AutoFixture/SsoConfigFixtures.cs +++ b/test/Core.Test/AutoFixture/SsoConfigFixtures.cs @@ -32,9 +32,7 @@ namespace Bit.Core.Test.AutoFixture.SsoConfigFixtures var fixture = new Fixture(); var ssoConfig = fixture.WithAutoNSubstitutions().Create(); var ssoConfigData = fixture.WithAutoNSubstitutions().Create(); - ssoConfig.Data = JsonSerializer.Serialize(ssoConfigData, new JsonSerializerOptions(){ - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - }); + ssoConfig.SetData(ssoConfigData); return ssoConfig; } } diff --git a/util/Migrator/DbScripts/2021-10-13_00_CryptoAgent.sql b/util/Migrator/DbScripts/2021-10-13_00_CryptoAgent.sql new file mode 100644 index 0000000000..5d43d0dddb --- /dev/null +++ b/util/Migrator/DbScripts/2021-10-13_00_CryptoAgent.sql @@ -0,0 +1,239 @@ +IF COL_LENGTH('[dbo].[User]', 'UsesCryptoAgent') IS NULL + BEGIN + ALTER TABLE + [dbo].[User] + ADD + [UsesCryptoAgent] BIT NULL + END +GO + +UPDATE + [dbo].[User] +SET + [UsesCryptoAgent] = 0 +WHERE + [UsesCryptoAgent] IS NULL +GO + +ALTER TABLE + [dbo].[User] +ALTER COLUMN + [UsesCryptoAgent] BIT NOT NULL +GO + +-- View: User +IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'UserView') + BEGIN + DROP VIEW [dbo].[UserView] + END +GO + +CREATE VIEW [dbo].[UserView] +AS +SELECT + * +FROM + [dbo].[User] +GO + +IF OBJECT_ID('[dbo].[User_Create]') IS NOT NULL + BEGIN + DROP PROCEDURE [dbo].[User_Create] + END +GO + +CREATE PROCEDURE [dbo].[User_Create] + @Id UNIQUEIDENTIFIER OUTPUT, + @Name NVARCHAR(50), + @Email NVARCHAR(256), + @EmailVerified BIT, + @MasterPassword NVARCHAR(300), + @MasterPasswordHint NVARCHAR(50), + @Culture NVARCHAR(10), + @SecurityStamp NVARCHAR(50), + @TwoFactorProviders NVARCHAR(MAX), + @TwoFactorRecoveryCode NVARCHAR(32), + @EquivalentDomains NVARCHAR(MAX), + @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), + @AccountRevisionDate DATETIME2(7), + @Key NVARCHAR(MAX), + @PublicKey NVARCHAR(MAX), + @PrivateKey NVARCHAR(MAX), + @Premium BIT, + @PremiumExpirationDate DATETIME2(7), + @RenewalReminderDate DATETIME2(7), + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @LicenseKey VARCHAR(100), + @Kdf TINYINT, + @KdfIterations INT, + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @ApiKey VARCHAR(30), + @ForcePasswordReset BIT = 0, + @UsesCryptoAgent BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + INSERT INTO [dbo].[User] + ( + [Id], + [Name], + [Email], + [EmailVerified], + [MasterPassword], + [MasterPasswordHint], + [Culture], + [SecurityStamp], + [TwoFactorProviders], + [TwoFactorRecoveryCode], + [EquivalentDomains], + [ExcludedGlobalEquivalentDomains], + [AccountRevisionDate], + [Key], + [PublicKey], + [PrivateKey], + [Premium], + [PremiumExpirationDate], + [RenewalReminderDate], + [Storage], + [MaxStorageGb], + [Gateway], + [GatewayCustomerId], + [GatewaySubscriptionId], + [ReferenceData], + [LicenseKey], + [Kdf], + [KdfIterations], + [CreationDate], + [RevisionDate], + [ApiKey], + [ForcePasswordReset], + [UsesCryptoAgent] + ) + VALUES + ( + @Id, + @Name, + @Email, + @EmailVerified, + @MasterPassword, + @MasterPasswordHint, + @Culture, + @SecurityStamp, + @TwoFactorProviders, + @TwoFactorRecoveryCode, + @EquivalentDomains, + @ExcludedGlobalEquivalentDomains, + @AccountRevisionDate, + @Key, + @PublicKey, + @PrivateKey, + @Premium, + @PremiumExpirationDate, + @RenewalReminderDate, + @Storage, + @MaxStorageGb, + @Gateway, + @GatewayCustomerId, + @GatewaySubscriptionId, + @ReferenceData, + @LicenseKey, + @Kdf, + @KdfIterations, + @CreationDate, + @RevisionDate, + @ApiKey, + @ForcePasswordReset, + @UsesCryptoAgent + ) +END +GO + +IF OBJECT_ID('[dbo].[User_Update]') IS NOT NULL + BEGIN + DROP PROCEDURE [dbo].[User_Update] + END +GO + +CREATE PROCEDURE [dbo].[User_Update] + @Id UNIQUEIDENTIFIER, + @Name NVARCHAR(50), + @Email NVARCHAR(256), + @EmailVerified BIT, + @MasterPassword NVARCHAR(300), + @MasterPasswordHint NVARCHAR(50), + @Culture NVARCHAR(10), + @SecurityStamp NVARCHAR(50), + @TwoFactorProviders NVARCHAR(MAX), + @TwoFactorRecoveryCode NVARCHAR(32), + @EquivalentDomains NVARCHAR(MAX), + @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), + @AccountRevisionDate DATETIME2(7), + @Key NVARCHAR(MAX), + @PublicKey NVARCHAR(MAX), + @PrivateKey NVARCHAR(MAX), + @Premium BIT, + @PremiumExpirationDate DATETIME2(7), + @RenewalReminderDate DATETIME2(7), + @Storage BIGINT, + @MaxStorageGb SMALLINT, + @Gateway TINYINT, + @GatewayCustomerId VARCHAR(50), + @GatewaySubscriptionId VARCHAR(50), + @ReferenceData VARCHAR(MAX), + @LicenseKey VARCHAR(100), + @Kdf TINYINT, + @KdfIterations INT, + @CreationDate DATETIME2(7), + @RevisionDate DATETIME2(7), + @ApiKey VARCHAR(30), + @ForcePasswordReset BIT = 0, + @UsesCryptoAgent BIT = 0 +AS +BEGIN + SET NOCOUNT ON + + UPDATE + [dbo].[User] + SET + [Name] = @Name, + [Email] = @Email, + [EmailVerified] = @EmailVerified, + [MasterPassword] = @MasterPassword, + [MasterPasswordHint] = @MasterPasswordHint, + [Culture] = @Culture, + [SecurityStamp] = @SecurityStamp, + [TwoFactorProviders] = @TwoFactorProviders, + [TwoFactorRecoveryCode] = @TwoFactorRecoveryCode, + [EquivalentDomains] = @EquivalentDomains, + [ExcludedGlobalEquivalentDomains] = @ExcludedGlobalEquivalentDomains, + [AccountRevisionDate] = @AccountRevisionDate, + [Key] = @Key, + [PublicKey] = @PublicKey, + [PrivateKey] = @PrivateKey, + [Premium] = @Premium, + [PremiumExpirationDate] = @PremiumExpirationDate, + [RenewalReminderDate] = @RenewalReminderDate, + [Storage] = @Storage, + [MaxStorageGb] = @MaxStorageGb, + [Gateway] = @Gateway, + [GatewayCustomerId] = @GatewayCustomerId, + [GatewaySubscriptionId] = @GatewaySubscriptionId, + [ReferenceData] = @ReferenceData, + [LicenseKey] = @LicenseKey, + [Kdf] = @Kdf, + [KdfIterations] = @KdfIterations, + [CreationDate] = @CreationDate, + [RevisionDate] = @RevisionDate, + [ApiKey] = @ApiKey, + [ForcePasswordReset] = @ForcePasswordReset, + [UsesCryptoAgent] = @UsesCryptoAgent + WHERE + [Id] = @Id +END