From fd37cb5a127510f1b650542e0087a41037807d49 Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 9 Nov 2021 16:37:32 +0100 Subject: [PATCH] Add support for Key Connector OTP and account migration (#1663) Co-authored-by: Thomas Rittson --- .../src/Sso/Controllers/AccountController.cs | 5 +- src/Api/Controllers/AccountsController.cs | 110 +- src/Api/Controllers/CiphersController.cs | 6 +- .../Controllers/OrganizationsController.cs | 23 +- src/Api/Controllers/TwoFactorController.cs | 60 +- src/Core/Enums/EventType.cs | 5 + .../IdentityServer/BaseRequestValidator.cs | 4 +- .../CustomTokenRequestValidator.cs | 57 +- .../ResourceOwnerPasswordValidator.cs | 3 +- .../Handlebars/OTPEmail.html.hbs | 14 + .../Handlebars/OTPEmail.text.hbs | 5 + .../Accounts/DeleteAccountRequestModel.cs | 10 - .../Api/Request/Accounts/EmailRequestModel.cs | 9 +- .../Accounts/EmailTokenRequestModel.cs | 6 +- .../Request/Accounts/PasswordRequestModel.cs | 8 +- .../SecretVerificationRequestModel.cs | 21 + .../Accounts/SecurityStampRequestModel.cs | 10 - ...l.cs => SetKeyConnectorKeyRequestModel.cs} | 2 +- .../VerifyOTPRequestModel.cs} | 4 +- .../Accounts/VerifyPasswordRequestModel.cs | 12 - .../Api/Request/CipherPurgeRequestModel.cs | 10 - .../OrganizationDeleteRequestModel.cs | 10 - .../OrganizationSsoRequestModel.cs | 45 +- .../Api/Request/TwoFactorRequestModels.cs | 23 +- .../Response/OrganizationUserResponseModel.cs | 2 + .../ProfileOrganizationResponseModel.cs | 8 + .../Api/Response/ProfileResponseModel.cs | 2 + .../OrganizationUserOrganizationDetails.cs | 1 + .../Data/OrganizationUserUserDetails.cs | 1 + src/Core/Models/Data/SsoConfigurationData.cs | 17 +- src/Core/Models/Table/SsoConfig.cs | 10 +- src/Core/Models/Table/User.cs | 2 +- ...izationUserOrganizationDetailsViewQuery.cs | 6 +- .../Queries/OrganizationUserUserViewQuery.cs | 1 + src/Core/Services/IMailService.cs | 1 + src/Core/Services/IUserService.cs | 6 +- .../Implementations/HandlebarsMailService.cs | 15 + .../Implementations/OrganizationService.cs | 11 + .../Services/Implementations/PolicyService.cs | 9 + .../Implementations/SsoConfigService.cs | 55 +- .../Services/Implementations/UserService.cs | 133 +- .../NoopImplementations/NoopMailService.cs | 5 + src/Core/Utilities/CoreHelpers.cs | 10 + .../Utilities/ServiceCollectionExtensions.cs | 2 +- src/Sql/dbo/Stored Procedures/User_Create.sql | 6 +- src/Sql/dbo/Stored Procedures/User_Update.sql | 4 +- src/Sql/dbo/Tables/User.sql | 2 +- ...rganizationUserOrganizationDetailsView.sql | 5 +- .../Views/OrganizationUserUserDetailsView.sql | 3 +- .../Controllers/AccountsControllerTests.cs | 15 +- .../Services/OrganizationServiceTests.cs | 33 + test/Core.Test/Services/PolicyServiceTests.cs | 35 + .../Services/SsoConfigServiceTests.cs | 67 +- ...ent.sql => 2021-11-08_00_KeyConnector.sql} | 105 +- .../20211108041911_KeyConnector.Designer.cs | 1495 ++++++++++++++++ .../Migrations/20211108041911_KeyConnector.cs | 24 + .../DatabaseContextModelSnapshot.cs | 3 + .../Scripts/2021-11-08_00_KeyConnector.sql | 9 + .../20211108041547_KeyConnector.Designer.cs | 1504 +++++++++++++++++ .../Migrations/20211108041547_KeyConnector.cs | 24 + .../DatabaseContextModelSnapshot.cs | 3 + .../Scripts/2021-11-08_00_KeyConnector.psql | 9 + 62 files changed, 3799 insertions(+), 306 deletions(-) create mode 100644 src/Core/MailTemplates/Handlebars/OTPEmail.html.hbs create mode 100644 src/Core/MailTemplates/Handlebars/OTPEmail.text.hbs delete mode 100644 src/Core/Models/Api/Request/Accounts/DeleteAccountRequestModel.cs create mode 100644 src/Core/Models/Api/Request/Accounts/SecretVerificationRequestModel.cs delete mode 100644 src/Core/Models/Api/Request/Accounts/SecurityStampRequestModel.cs rename src/Core/Models/Api/Request/Accounts/{SetCryptoAgentKeyRequestModel.cs => SetKeyConnectorKeyRequestModel.cs} (94%) rename src/Core/Models/Api/Request/{ApiKeyRequestModel.cs => Accounts/VerifyOTPRequestModel.cs} (55%) delete mode 100644 src/Core/Models/Api/Request/Accounts/VerifyPasswordRequestModel.cs delete mode 100644 src/Core/Models/Api/Request/CipherPurgeRequestModel.cs delete mode 100644 src/Core/Models/Api/Request/Organizations/OrganizationDeleteRequestModel.cs rename util/Migrator/DbScripts/{2021-10-13_00_CryptoAgent.sql => 2021-11-08_00_KeyConnector.sql} (70%) create mode 100644 util/MySqlMigrations/Migrations/20211108041911_KeyConnector.Designer.cs create mode 100644 util/MySqlMigrations/Migrations/20211108041911_KeyConnector.cs create mode 100644 util/MySqlMigrations/Scripts/2021-11-08_00_KeyConnector.sql create mode 100644 util/PostgresMigrations/Migrations/20211108041547_KeyConnector.Designer.cs create mode 100644 util/PostgresMigrations/Migrations/20211108041547_KeyConnector.cs create mode 100644 util/PostgresMigrations/Scripts/2021-11-08_00_KeyConnector.psql diff --git a/bitwarden_license/src/Sso/Controllers/AccountController.cs b/bitwarden_license/src/Sso/Controllers/AccountController.cs index d39a1d930..3ef99f0fa 100644 --- a/bitwarden_license/src/Sso/Controllers/AccountController.cs +++ b/bitwarden_license/src/Sso/Controllers/AccountController.cs @@ -604,14 +604,15 @@ namespace Bit.Sso.Controllers await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_ResetSsoLink); } } + private async Task CreateSsoUserRecord(string providerUserId, Guid userId, Guid orgId) { var ssoUser = new SsoUser { ExternalId = providerUserId, UserId = userId, - OrganizationId = orgId - }; + OrganizationId = orgId, + }; await _ssoUserRepository.CreateAsync(ssoUser); } diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index e2dc8b20c..9232b011e 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using Bit.Core.Enums.Provider; @@ -46,7 +45,6 @@ namespace Bit.Api.Controllers IOrganizationUserRepository organizationUserRepository, IProviderUserRepository providerUserRepository, IPaymentService paymentService, - ISsoUserRepository ssoUserRepository, IUserRepository userRepository, IUserService userService, ISendRepository sendRepository, @@ -118,6 +116,11 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } + if (user.UsesKeyConnector) + { + throw new BadRequestException("You cannot change your email when using Key Connector."); + } + if (!await _userService.CheckPasswordAsync(user, model.MasterPasswordHash)) { await Task.Delay(2000); @@ -136,6 +139,11 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } + if (user.UsesKeyConnector) + { + throw new BadRequestException("You cannot change your email when using Key Connector."); + } + var result = await _userService.ChangeEmailAsync(user, model.MasterPasswordHash, model.NewEmail, model.NewMasterPasswordHash, model.Token, model.Key); if (result.Succeeded) @@ -238,7 +246,7 @@ namespace Bit.Api.Controllers } [HttpPost("verify-password")] - public async Task PostVerifyPassword([FromBody]VerifyPasswordRequestModel model) + public async Task PostVerifyPassword([FromBody]SecretVerificationRequestModel model) { var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) @@ -256,8 +264,8 @@ namespace Bit.Api.Controllers throw new BadRequestException(ModelState); } - [HttpPost("set-crypto-agent-key")] - public async Task PostSetCryptoAgentKeyAsync([FromBody]SetCryptoAgentKeyRequestModel model) + [HttpPost("set-key-connector-key")] + public async Task PostSetKeyConnectorKeyAsync([FromBody]SetKeyConnectorKeyRequestModel model) { var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) @@ -265,7 +273,30 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } - var result = await _userService.SetCryptoAgentKeyAsync(model.ToUser(user), model.Key, model.OrgIdentifier); + var result = await _userService.SetKeyConnectorKeyAsync(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("convert-to-key-connector")] + public async Task PostConvertToKeyConnector() + { + var user = await _userService.GetUserByPrincipalAsync(User); + if (user == null) + { + throw new UnauthorizedAccessException(); + } + + var result = await _userService.ConvertToKeyConnectorAsync(user); if (result.Succeeded) { return; @@ -361,7 +392,7 @@ namespace Bit.Api.Controllers } [HttpPost("security-stamp")] - public async Task PostSecurityStamp([FromBody]SecurityStampRequestModel model) + public async Task PostSecurityStamp([FromBody]SecretVerificationRequestModel model) { var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) @@ -369,7 +400,7 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } - var result = await _userService.RefreshSecurityStampAsync(user, model.MasterPasswordHash); + var result = await _userService.RefreshSecurityStampAsync(user, model.Secret); if (result.Succeeded) { return; @@ -471,7 +502,7 @@ namespace Bit.Api.Controllers [HttpDelete] [HttpPost("delete")] - public async Task Delete([FromBody]DeleteAccountRequestModel model) + public async Task Delete([FromBody]SecretVerificationRequestModel model) { var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) @@ -479,9 +510,9 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } - if (!await _userService.CheckPasswordAsync(user, model.MasterPasswordHash)) + if (!await _userService.VerifySecretAsync(user, model.Secret)) { - ModelState.AddModelError("MasterPasswordHash", "Invalid password."); + ModelState.AddModelError(string.Empty, "User verification failed."); await Task.Delay(2000); } else @@ -761,7 +792,7 @@ namespace Bit.Api.Controllers } [HttpPost("api-key")] - public async Task ApiKey([FromBody]ApiKeyRequestModel model) + public async Task ApiKey([FromBody]SecretVerificationRequestModel model) { var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) @@ -769,20 +800,17 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } - if (!await _userService.CheckPasswordAsync(user, model.MasterPasswordHash)) + if (!await _userService.VerifySecretAsync(user, model.Secret)) { await Task.Delay(2000); - throw new BadRequestException("MasterPasswordHash", "Invalid password."); - } - else - { - var response = new ApiKeyResponseModel(user); - return response; + throw new BadRequestException(string.Empty, "User verification failed."); } + + return new ApiKeyResponseModel(user); } [HttpPost("rotate-api-key")] - public async Task RotateApiKey([FromBody]ApiKeyRequestModel model) + public async Task RotateApiKey([FromBody]SecretVerificationRequestModel model) { var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) @@ -790,17 +818,15 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } - if (!await _userService.CheckPasswordAsync(user, model.MasterPasswordHash)) + if (!await _userService.VerifySecretAsync(user, model.Secret)) { await Task.Delay(2000); - throw new BadRequestException("MasterPasswordHash", "Invalid password."); - } - else - { - await _userService.RotateApiKeyAsync(user); - var response = new ApiKeyResponseModel(user); - return response; + throw new BadRequestException(string.Empty, "User verification failed."); } + + await _userService.RotateApiKeyAsync(user); + var response = new ApiKeyResponseModel(user); + return response; } [HttpPut("update-temp-password")] @@ -825,5 +851,33 @@ namespace Bit.Api.Controllers throw new BadRequestException(ModelState); } + + [HttpPost("request-otp")] + public async Task PostRequestOTP() + { + var user = await _userService.GetUserByPrincipalAsync(User); + if (user is not { UsesKeyConnector: true }) + { + throw new UnauthorizedAccessException(); + } + + await _userService.SendOTPAsync(user); + } + + [HttpPost("verify-otp")] + public async Task VerifyOTP([FromBody]VerifyOTPRequestModel model) + { + var user = await _userService.GetUserByPrincipalAsync(User); + if (user is not { UsesKeyConnector: true }) + { + throw new UnauthorizedAccessException(); + } + + if (!await _userService.VerifyOTPAsync(user, model.OTP)) + { + await Task.Delay(2000); + throw new BadRequestException("Token", "Invalid token"); + } + } } } diff --git a/src/Api/Controllers/CiphersController.cs b/src/Api/Controllers/CiphersController.cs index f4a435290..b19f4f453 100644 --- a/src/Api/Controllers/CiphersController.cs +++ b/src/Api/Controllers/CiphersController.cs @@ -550,7 +550,7 @@ namespace Bit.Api.Controllers } [HttpPost("purge")] - public async Task PostPurge([FromBody]CipherPurgeRequestModel model, string organizationId = null) + public async Task PostPurge([FromBody]SecretVerificationRequestModel model, string organizationId = null) { var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) @@ -558,9 +558,9 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } - if (!await _userService.CheckPasswordAsync(user, model.MasterPasswordHash)) + if (!await _userService.VerifySecretAsync(user, model.Secret)) { - ModelState.AddModelError("MasterPasswordHash", "Invalid password."); + ModelState.AddModelError(string.Empty, "User verification failed."); await Task.Delay(2000); throw new BadRequestException(ModelState); } diff --git a/src/Api/Controllers/OrganizationsController.cs b/src/Api/Controllers/OrganizationsController.cs index 70f5ed1eb..3d9023137 100644 --- a/src/Api/Controllers/OrganizationsController.cs +++ b/src/Api/Controllers/OrganizationsController.cs @@ -390,7 +390,7 @@ namespace Bit.Api.Controllers [HttpDelete("{id}")] [HttpPost("{id}/delete")] - public async Task Delete(string id, [FromBody]OrganizationDeleteRequestModel model) + public async Task Delete(string id, [FromBody]SecretVerificationRequestModel model) { var orgIdGuid = new Guid(id); if (!await _currentContext.OrganizationOwner(orgIdGuid)) @@ -410,10 +410,10 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } - if (!await _userService.CheckPasswordAsync(user, model.MasterPasswordHash)) + if (!await _userService.VerifySecretAsync(user, model.Secret)) { await Task.Delay(2000); - throw new BadRequestException("MasterPasswordHash", "Invalid password."); + throw new BadRequestException(string.Empty, "User verification failed."); } else { @@ -466,7 +466,7 @@ namespace Bit.Api.Controllers } [HttpPost("{id}/api-key")] - public async Task ApiKey(string id, [FromBody]ApiKeyRequestModel model) + public async Task ApiKey(string id, [FromBody]SecretVerificationRequestModel model) { var orgIdGuid = new Guid(id); if (!await _currentContext.OrganizationOwner(orgIdGuid)) @@ -486,7 +486,7 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } - if (!await _userService.CheckPasswordAsync(user, model.MasterPasswordHash)) + if (!await _userService.VerifySecretAsync(user, model.Secret)) { await Task.Delay(2000); throw new BadRequestException("MasterPasswordHash", "Invalid password."); @@ -499,7 +499,7 @@ namespace Bit.Api.Controllers } [HttpPost("{id}/rotate-api-key")] - public async Task RotateApiKey(string id, [FromBody]ApiKeyRequestModel model) + public async Task RotateApiKey(string id, [FromBody]SecretVerificationRequestModel model) { var orgIdGuid = new Guid(id); if (!await _currentContext.OrganizationOwner(orgIdGuid)) @@ -519,7 +519,7 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } - if (!await _userService.CheckPasswordAsync(user, model.MasterPasswordHash)) + if (!await _userService.VerifySecretAsync(user, model.Secret)) { await Task.Delay(2000); throw new BadRequestException("MasterPasswordHash", "Invalid password."); @@ -640,14 +640,7 @@ namespace Bit.Api.Controllers } var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(id); - if (ssoConfig == null) - { - ssoConfig = model.ToSsoConfig(id); - } - else - { - ssoConfig = model.ToSsoConfig(ssoConfig); - } + ssoConfig = ssoConfig == null ? model.ToSsoConfig(id) : model.ToSsoConfig(ssoConfig); await _ssoConfigService.SaveAsync(ssoConfig); diff --git a/src/Api/Controllers/TwoFactorController.cs b/src/Api/Controllers/TwoFactorController.cs index 532239ed2..59242ef7e 100644 --- a/src/Api/Controllers/TwoFactorController.cs +++ b/src/Api/Controllers/TwoFactorController.cs @@ -80,9 +80,9 @@ namespace Bit.Api.Controllers } [HttpPost("get-authenticator")] - public async Task GetAuthenticator([FromBody]TwoFactorRequestModel model) + public async Task GetAuthenticator([FromBody]SecretVerificationRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, false); + var user = await CheckAsync(model, false); var response = new TwoFactorAuthenticatorResponseModel(user); return response; } @@ -92,7 +92,7 @@ namespace Bit.Api.Controllers public async Task PutAuthenticator( [FromBody]UpdateTwoFactorAuthenticatorRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, false); + var user = await CheckAsync(model, false); model.ToUser(user); if (!await _userManager.VerifyTwoFactorTokenAsync(user, @@ -108,9 +108,9 @@ namespace Bit.Api.Controllers } [HttpPost("get-yubikey")] - public async Task GetYubiKey([FromBody]TwoFactorRequestModel model) + public async Task GetYubiKey([FromBody]SecretVerificationRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, true); + var user = await CheckAsync(model, true); var response = new TwoFactorYubiKeyResponseModel(user); return response; } @@ -119,7 +119,7 @@ namespace Bit.Api.Controllers [HttpPost("yubikey")] public async Task PutYubiKey([FromBody]UpdateTwoFactorYubicoOtpRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, true); + var user = await CheckAsync(model, true); model.ToUser(user); await ValidateYubiKeyAsync(user, nameof(model.Key1), model.Key1); @@ -134,9 +134,9 @@ namespace Bit.Api.Controllers } [HttpPost("get-duo")] - public async Task GetDuo([FromBody]TwoFactorRequestModel model) + public async Task GetDuo([FromBody]SecretVerificationRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, true); + var user = await CheckAsync(model, true); var response = new TwoFactorDuoResponseModel(user); return response; } @@ -145,7 +145,7 @@ namespace Bit.Api.Controllers [HttpPost("duo")] public async Task PutDuo([FromBody]UpdateTwoFactorDuoRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, true); + var user = await CheckAsync(model, true); try { var duoApi = new DuoApi(model.IntegrationKey, model.SecretKey, model.Host); @@ -164,9 +164,9 @@ namespace Bit.Api.Controllers [HttpPost("~/organizations/{id}/two-factor/get-duo")] public async Task GetOrganizationDuo(string id, - [FromBody]TwoFactorRequestModel model) + [FromBody]SecretVerificationRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, false); + var user = await CheckAsync(model, false); var orgIdGuid = new Guid(id); if (!await _currentContext.ManagePolicies(orgIdGuid)) @@ -189,7 +189,7 @@ namespace Bit.Api.Controllers public async Task PutOrganizationDuo(string id, [FromBody]UpdateTwoFactorDuoRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, false); + var user = await CheckAsync(model, false); var orgIdGuid = new Guid(id); if (!await _currentContext.ManagePolicies(orgIdGuid)) @@ -221,17 +221,17 @@ namespace Bit.Api.Controllers } [HttpPost("get-webauthn")] - public async Task GetWebAuthn([FromBody]TwoFactorRequestModel model) + public async Task GetWebAuthn([FromBody]SecretVerificationRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, true); + var user = await CheckAsync(model, true); var response = new TwoFactorWebAuthnResponseModel(user); return response; } [HttpPost("get-webauthn-challenge")] - public async Task GetWebAuthnChallenge([FromBody]TwoFactorRequestModel model) + public async Task GetWebAuthnChallenge([FromBody]SecretVerificationRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, true); + var user = await CheckAsync(model, true); var reg = await _userService.StartWebAuthnRegistrationAsync(user); return reg; } @@ -240,7 +240,7 @@ namespace Bit.Api.Controllers [HttpPost("webauthn")] public async Task PutWebAuthn([FromBody]TwoFactorWebAuthnRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, true); + var user = await CheckAsync(model, true); var success = await _userService.CompleteWebAuthRegistrationAsync( user, model.Id.Value, model.Name, model.DeviceResponse); @@ -255,16 +255,16 @@ namespace Bit.Api.Controllers [HttpDelete("webauthn")] public async Task DeleteWebAuthn([FromBody]TwoFactorWebAuthnDeleteRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, true); + var user = await CheckAsync(model, true); await _userService.DeleteWebAuthnKeyAsync(user, model.Id.Value); var response = new TwoFactorWebAuthnResponseModel(user); return response; } [HttpPost("get-email")] - public async Task GetEmail([FromBody]TwoFactorRequestModel model) + public async Task GetEmail([FromBody]SecretVerificationRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, false); + var user = await CheckAsync(model, false); var response = new TwoFactorEmailResponseModel(user); return response; } @@ -272,7 +272,7 @@ namespace Bit.Api.Controllers [HttpPost("send-email")] public async Task SendEmail([FromBody]TwoFactorEmailRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, false); + var user = await CheckAsync(model, false); model.ToUser(user); await _userService.SendTwoFactorEmailAsync(user); } @@ -284,7 +284,7 @@ namespace Bit.Api.Controllers var user = await _userManager.FindByEmailAsync(model.Email.ToLowerInvariant()); if (user != null) { - if (await _userService.CheckPasswordAsync(user, model.MasterPasswordHash)) + if (await _userService.VerifySecretAsync(user, model.Secret)) { await _userService.SendTwoFactorEmailAsync(user); return; @@ -299,7 +299,7 @@ namespace Bit.Api.Controllers [HttpPost("email")] public async Task PutEmail([FromBody]UpdateTwoFactorEmailRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, false); + var user = await CheckAsync(model, false); model.ToUser(user); if (!await _userManager.VerifyTwoFactorTokenAsync(user, @@ -318,7 +318,7 @@ namespace Bit.Api.Controllers [HttpPost("disable")] public async Task PutDisable([FromBody]TwoFactorProviderRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, false); + var user = await CheckAsync(model, false); await _userService.DisableTwoFactorProviderAsync(user, model.Type.Value, _organizationService); var response = new TwoFactorProviderResponseModel(model.Type.Value, user); return response; @@ -329,7 +329,7 @@ namespace Bit.Api.Controllers public async Task PutOrganizationDisable(string id, [FromBody]TwoFactorProviderRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, false); + var user = await CheckAsync(model, false); var orgIdGuid = new Guid(id); if (!await _currentContext.ManagePolicies(orgIdGuid)) @@ -349,9 +349,9 @@ namespace Bit.Api.Controllers } [HttpPost("get-recover")] - public async Task GetRecover([FromBody]TwoFactorRequestModel model) + public async Task GetRecover([FromBody]SecretVerificationRequestModel model) { - var user = await CheckAsync(model.MasterPasswordHash, false); + var user = await CheckAsync(model, false); var response = new TwoFactorRecoverResponseModel(user); return response; } @@ -368,7 +368,7 @@ namespace Bit.Api.Controllers } } - private async Task CheckAsync(string masterPasswordHash, bool premium) + private async Task CheckAsync(SecretVerificationRequestModel model, bool premium) { var user = await _userService.GetUserByPrincipalAsync(User); if (user == null) @@ -376,10 +376,10 @@ namespace Bit.Api.Controllers throw new UnauthorizedAccessException(); } - if (!await _userService.CheckPasswordAsync(user, masterPasswordHash)) + if (!await _userService.VerifySecretAsync(user, model.Secret)) { await Task.Delay(2000); - throw new BadRequestException("MasterPasswordHash", "Invalid password."); + throw new BadRequestException(string.Empty, "User verification failed."); } if (premium && !(await _userService.CanAccessPremium(user))) diff --git a/src/Core/Enums/EventType.cs b/src/Core/Enums/EventType.cs index bb6aca2c6..54fbcf8d6 100644 --- a/src/Core/Enums/EventType.cs +++ b/src/Core/Enums/EventType.cs @@ -11,6 +11,7 @@ User_FailedLogIn2fa = 1006, User_ClientExportedVault = 1007, User_UpdatedTempPassword = 1008, + User_MigratedKeyToKeyConnector = 1009, Cipher_Created = 1100, Cipher_Updated = 1101, @@ -54,6 +55,10 @@ Organization_PurgedVault = 1601, // Organization_ClientExportedVault = 1602, Organization_VaultAccessed = 1603, + Organization_EnabledSso = 1604, + Organization_DisabledSso = 1605, + Organization_EnabledKeyConnector = 1606, + Organization_DisabledKeyConnector = 1607, Policy_Updated = 1700, diff --git a/src/Core/IdentityServer/BaseRequestValidator.cs b/src/Core/IdentityServer/BaseRequestValidator.cs index 7eb9052ad..972df3603 100644 --- a/src/Core/IdentityServer/BaseRequestValidator.cs +++ b/src/Core/IdentityServer/BaseRequestValidator.cs @@ -176,7 +176,7 @@ namespace Bit.Core.IdentityServer customResponse.Add("TwoFactorToken", token); } - SetSuccessResult(context, user, claims, customResponse); + await SetSuccessResult(context, user, claims, customResponse); } protected async Task BuildTwoFactorResultAsync(User user, Organization organization, T context) @@ -256,7 +256,7 @@ namespace Bit.Core.IdentityServer protected abstract void SetSsoResult(T context, Dictionary customResponse); - protected abstract void SetSuccessResult(T context, User user, List claims, + protected abstract Task SetSuccessResult(T context, User user, List claims, Dictionary customResponse); protected abstract void SetErrorResult(T context, Dictionary customResponse); diff --git a/src/Core/IdentityServer/CustomTokenRequestValidator.cs b/src/Core/IdentityServer/CustomTokenRequestValidator.cs index 73af2070f..0b9eb8c61 100644 --- a/src/Core/IdentityServer/CustomTokenRequestValidator.cs +++ b/src/Core/IdentityServer/CustomTokenRequestValidator.cs @@ -24,6 +24,7 @@ namespace Bit.Core.IdentityServer { private UserManager _userManager; private readonly ISsoConfigRepository _ssoConfigRepository; + private readonly IOrganizationRepository _organizationRepository; public CustomTokenRequestValidator( UserManager userManager, @@ -47,6 +48,7 @@ namespace Bit.Core.IdentityServer { _userManager = userManager; _ssoConfigRepository = ssoConfigRepository; + _organizationRepository = organizationRepository; } public async Task ValidateAsync(CustomTokenRequestValidationContext context) @@ -58,25 +60,6 @@ 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) @@ -87,7 +70,7 @@ namespace Bit.Core.IdentityServer return (user, user != null); } - protected override void SetSuccessResult(CustomTokenRequestValidationContext context, User user, + protected override async Task SetSuccessResult(CustomTokenRequestValidationContext context, User user, List claims, Dictionary customResponse) { context.Result.CustomResponse = customResponse; @@ -100,6 +83,40 @@ namespace Bit.Core.IdentityServer context.Result.ValidatedRequest.ClientClaims.Add(claim); } } + + if (context.Result.CustomResponse == null || user.MasterPassword != null) + { + return; + } + + // KeyConnector responses below + + // Apikey login + if (context.Result.ValidatedRequest.GrantType == "client_credentials") + { + if (user.UsesKeyConnector) { + // KeyConnectorUrl is configured in the CLI client, just disable master password reset + context.Result.CustomResponse["ResetMasterPassword"] = false; + } + return; + } + + // SSO login + var organizationClaim = context.Result.ValidatedRequest.Subject?.FindFirst(c => c.Type == "organizationId"); + if (organizationClaim?.Value != null) + { + var organizationId = new Guid(organizationClaim.Value); + + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organizationId); + var ssoConfigData = ssoConfig.GetData(); + + if (ssoConfigData is { UseKeyConnector: true } && !string.IsNullOrEmpty(ssoConfigData.KeyConnectorUrl)) + { + context.Result.CustomResponse["KeyConnectorUrl"] = ssoConfigData.KeyConnectorUrl; + // Prevent clients redirecting to set-password + context.Result.CustomResponse["ResetMasterPassword"] = false; + } + } } protected override void SetTwoFactorResult(CustomTokenRequestValidationContext context, diff --git a/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs b/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs index 9a3fa707c..2f9387a75 100644 --- a/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs +++ b/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs @@ -106,13 +106,14 @@ namespace Bit.Core.IdentityServer return (user, true); } - protected override void SetSuccessResult(ResourceOwnerPasswordValidationContext context, User user, + protected override Task SetSuccessResult(ResourceOwnerPasswordValidationContext context, User user, List claims, Dictionary customResponse) { context.Result = new GrantValidationResult(user.Id.ToString(), "Application", identityProvider: "bitwarden", claims: claims.Count > 0 ? claims : null, customResponse: customResponse); + return Task.CompletedTask; } protected override void SetTwoFactorResult(ResourceOwnerPasswordValidationContext context, diff --git a/src/Core/MailTemplates/Handlebars/OTPEmail.html.hbs b/src/Core/MailTemplates/Handlebars/OTPEmail.html.hbs new file mode 100644 index 000000000..9a11199b2 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/OTPEmail.html.hbs @@ -0,0 +1,14 @@ +{{#>FullHtmlLayout}} + + + + + + + +
+ Your email verification code is: {{Token}} +
+ Use this code to complete the protected action in Bitwarden. +
+{{/FullHtmlLayout}} diff --git a/src/Core/MailTemplates/Handlebars/OTPEmail.text.hbs b/src/Core/MailTemplates/Handlebars/OTPEmail.text.hbs new file mode 100644 index 000000000..9947300d8 --- /dev/null +++ b/src/Core/MailTemplates/Handlebars/OTPEmail.text.hbs @@ -0,0 +1,5 @@ +{{#>BasicTextLayout}} +Your email verification code is: {{Token}} + +Use this code to complete the protected action in Bitwarden. +{{/BasicTextLayout}} diff --git a/src/Core/Models/Api/Request/Accounts/DeleteAccountRequestModel.cs b/src/Core/Models/Api/Request/Accounts/DeleteAccountRequestModel.cs deleted file mode 100644 index 3fd15080e..000000000 --- a/src/Core/Models/Api/Request/Accounts/DeleteAccountRequestModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Bit.Core.Models.Api -{ - public class DeleteAccountRequestModel - { - [Required] - public string MasterPasswordHash { get; set; } - } -} diff --git a/src/Core/Models/Api/Request/Accounts/EmailRequestModel.cs b/src/Core/Models/Api/Request/Accounts/EmailRequestModel.cs index b62a06b18..1ae39e8dd 100644 --- a/src/Core/Models/Api/Request/Accounts/EmailRequestModel.cs +++ b/src/Core/Models/Api/Request/Accounts/EmailRequestModel.cs @@ -1,10 +1,10 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; using Bit.Core.Utilities; +using Bit.Core.Models.Api; namespace Bit.Core.Models.Api { - public class EmailRequestModel + public class EmailRequestModel : SecretVerificationRequestModel { [Required] [StrictEmailAddress] @@ -12,9 +12,6 @@ namespace Bit.Core.Models.Api public string NewEmail { get; set; } [Required] [StringLength(300)] - public string MasterPasswordHash { get; set; } - [Required] - [StringLength(300)] public string NewMasterPasswordHash { get; set; } [Required] public string Token { get; set; } diff --git a/src/Core/Models/Api/Request/Accounts/EmailTokenRequestModel.cs b/src/Core/Models/Api/Request/Accounts/EmailTokenRequestModel.cs index 1228fa9b9..e12826da3 100644 --- a/src/Core/Models/Api/Request/Accounts/EmailTokenRequestModel.cs +++ b/src/Core/Models/Api/Request/Accounts/EmailTokenRequestModel.cs @@ -1,16 +1,14 @@ using System.ComponentModel.DataAnnotations; using Bit.Core.Utilities; +using Bit.Core.Models.Api; namespace Bit.Core.Models.Api { - public class EmailTokenRequestModel + public class EmailTokenRequestModel : SecretVerificationRequestModel { [Required] [StrictEmailAddress] [StringLength(256)] public string NewEmail { get; set; } - [Required] - [StringLength(300)] - public string MasterPasswordHash { get; set; } } } diff --git a/src/Core/Models/Api/Request/Accounts/PasswordRequestModel.cs b/src/Core/Models/Api/Request/Accounts/PasswordRequestModel.cs index d3d5a72fc..3cadaa4a6 100644 --- a/src/Core/Models/Api/Request/Accounts/PasswordRequestModel.cs +++ b/src/Core/Models/Api/Request/Accounts/PasswordRequestModel.cs @@ -1,13 +1,9 @@ -using System; -using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations; namespace Bit.Core.Models.Api { - public class PasswordRequestModel + public class PasswordRequestModel : SecretVerificationRequestModel { - [Required] - [StringLength(300)] - public string MasterPasswordHash { get; set; } [Required] [StringLength(300)] public string NewMasterPasswordHash { get; set; } diff --git a/src/Core/Models/Api/Request/Accounts/SecretVerificationRequestModel.cs b/src/Core/Models/Api/Request/Accounts/SecretVerificationRequestModel.cs new file mode 100644 index 000000000..03878592b --- /dev/null +++ b/src/Core/Models/Api/Request/Accounts/SecretVerificationRequestModel.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Bit.Core.Models.Api +{ + public class SecretVerificationRequestModel : IValidatableObject + { + [StringLength(300)] + public string MasterPasswordHash { get; set; } + public string OTP { get; set; } + public string Secret => !string.IsNullOrEmpty(MasterPasswordHash) ? MasterPasswordHash : OTP; + + public IEnumerable Validate(ValidationContext validationContext) + { + if (string.IsNullOrEmpty(Secret)) + { + yield return new ValidationResult("MasterPasswordHash or OTP must be supplied."); + } + } + } +} diff --git a/src/Core/Models/Api/Request/Accounts/SecurityStampRequestModel.cs b/src/Core/Models/Api/Request/Accounts/SecurityStampRequestModel.cs deleted file mode 100644 index 9aaa48845..000000000 --- a/src/Core/Models/Api/Request/Accounts/SecurityStampRequestModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Bit.Core.Models.Api -{ - public class SecurityStampRequestModel - { - [Required] - public string MasterPasswordHash { get; set; } - } -} diff --git a/src/Core/Models/Api/Request/Accounts/SetCryptoAgentKeyRequestModel.cs b/src/Core/Models/Api/Request/Accounts/SetKeyConnectorKeyRequestModel.cs similarity index 94% rename from src/Core/Models/Api/Request/Accounts/SetCryptoAgentKeyRequestModel.cs rename to src/Core/Models/Api/Request/Accounts/SetKeyConnectorKeyRequestModel.cs index 01041db3d..93e8f39e8 100644 --- a/src/Core/Models/Api/Request/Accounts/SetCryptoAgentKeyRequestModel.cs +++ b/src/Core/Models/Api/Request/Accounts/SetKeyConnectorKeyRequestModel.cs @@ -4,7 +4,7 @@ using Bit.Core.Models.Table; namespace Bit.Core.Models.Api.Request.Accounts { - public class SetCryptoAgentKeyRequestModel + public class SetKeyConnectorKeyRequestModel { [Required] public string Key { get; set; } diff --git a/src/Core/Models/Api/Request/ApiKeyRequestModel.cs b/src/Core/Models/Api/Request/Accounts/VerifyOTPRequestModel.cs similarity index 55% rename from src/Core/Models/Api/Request/ApiKeyRequestModel.cs rename to src/Core/Models/Api/Request/Accounts/VerifyOTPRequestModel.cs index 6166a31e4..7719dcb1f 100644 --- a/src/Core/Models/Api/Request/ApiKeyRequestModel.cs +++ b/src/Core/Models/Api/Request/Accounts/VerifyOTPRequestModel.cs @@ -2,9 +2,9 @@ namespace Bit.Core.Models.Api { - public class ApiKeyRequestModel + public class VerifyOTPRequestModel { [Required] - public string MasterPasswordHash { get; set; } + public string OTP { get; set; } } } diff --git a/src/Core/Models/Api/Request/Accounts/VerifyPasswordRequestModel.cs b/src/Core/Models/Api/Request/Accounts/VerifyPasswordRequestModel.cs deleted file mode 100644 index fe03ef1c7..000000000 --- a/src/Core/Models/Api/Request/Accounts/VerifyPasswordRequestModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.ComponentModel.DataAnnotations; - -namespace Bit.Core.Models.Api -{ - public class VerifyPasswordRequestModel - { - [Required] - [StringLength(300)] - public string MasterPasswordHash { get; set; } - } -} diff --git a/src/Core/Models/Api/Request/CipherPurgeRequestModel.cs b/src/Core/Models/Api/Request/CipherPurgeRequestModel.cs deleted file mode 100644 index e620b7b10..000000000 --- a/src/Core/Models/Api/Request/CipherPurgeRequestModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Bit.Core.Models.Api -{ - public class CipherPurgeRequestModel - { - [Required] - public string MasterPasswordHash { get; set; } - } -} diff --git a/src/Core/Models/Api/Request/Organizations/OrganizationDeleteRequestModel.cs b/src/Core/Models/Api/Request/Organizations/OrganizationDeleteRequestModel.cs deleted file mode 100644 index b18388e8e..000000000 --- a/src/Core/Models/Api/Request/Organizations/OrganizationDeleteRequestModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Bit.Core.Models.Api -{ - public class OrganizationDeleteRequestModel - { - [Required] - public string MasterPasswordHash { get; set; } - } -} diff --git a/src/Core/Models/Api/Request/Organizations/OrganizationSsoRequestModel.cs b/src/Core/Models/Api/Request/Organizations/OrganizationSsoRequestModel.cs index 83ee2494f..f87ddfdf7 100644 --- a/src/Core/Models/Api/Request/Organizations/OrganizationSsoRequestModel.cs +++ b/src/Core/Models/Api/Request/Organizations/OrganizationSsoRequestModel.cs @@ -8,7 +8,6 @@ using Bit.Core.Sso; using U2F.Core.Utils; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Text.Json; using System.Text.RegularExpressions; using Bit.Core.Models.Table; using Microsoft.AspNetCore.Authentication.OpenIdConnect; @@ -40,47 +39,11 @@ namespace Bit.Core.Models.Api { public SsoConfigurationDataRequest() {} - public SsoConfigurationDataRequest(SsoConfigurationData configurationData) - { - ConfigType = configurationData.ConfigType; - UseCryptoAgent = configurationData.UseCryptoAgent; - CryptoAgentUrl = configurationData.CryptoAgentUrl; - Authority = configurationData.Authority; - ClientId = configurationData.ClientId; - ClientSecret = configurationData.ClientSecret; - MetadataAddress = configurationData.MetadataAddress; - RedirectBehavior = configurationData.RedirectBehavior; - GetClaimsFromUserInfoEndpoint = configurationData.GetClaimsFromUserInfoEndpoint; - IdpEntityId = configurationData.IdpEntityId; - IdpBindingType = configurationData.IdpBindingType; - IdpSingleSignOnServiceUrl = configurationData.IdpSingleSignOnServiceUrl; - IdpSingleLogoutServiceUrl = configurationData.IdpSingleLogoutServiceUrl; - IdpArtifactResolutionServiceUrl = configurationData.IdpArtifactResolutionServiceUrl; - IdpX509PublicCert = configurationData.IdpX509PublicCert; - IdpOutboundSigningAlgorithm = configurationData.IdpOutboundSigningAlgorithm; - IdpAllowUnsolicitedAuthnResponse = configurationData.IdpAllowUnsolicitedAuthnResponse; - IdpDisableOutboundLogoutRequests = configurationData.IdpDisableOutboundLogoutRequests; - IdpWantAuthnRequestsSigned = configurationData.IdpWantAuthnRequestsSigned; - SpNameIdFormat = configurationData.SpNameIdFormat; - SpOutboundSigningAlgorithm = configurationData.SpOutboundSigningAlgorithm ?? SamlSigningAlgorithms.Sha256; - SpSigningBehavior = configurationData.SpSigningBehavior; - SpWantAssertionsSigned = configurationData.SpWantAssertionsSigned; - SpValidateCertificates = configurationData.SpValidateCertificates; - SpMinIncomingSigningAlgorithm = configurationData.SpMinIncomingSigningAlgorithm ?? SamlSigningAlgorithms.Sha256; - AdditionalScopes = configurationData.AdditionalScopes; - AdditionalUserIdClaimTypes = configurationData.AdditionalUserIdClaimTypes; - AdditionalEmailClaimTypes = configurationData.AdditionalEmailClaimTypes; - AdditionalNameClaimTypes = configurationData.AdditionalNameClaimTypes; - AcrValues = configurationData.AcrValues; - ExpectedReturnAcrValue = configurationData.ExpectedReturnAcrValue; - } - [Required] public SsoType ConfigType { get; set; } - // Crypto Agent - public bool UseCryptoAgent { get; set; } - public string CryptoAgentUrl { get; set; } + public bool UseKeyConnector { get; set; } + public string KeyConnectorUrl { get; set; } // OIDC public string Authority { get; set; } @@ -215,8 +178,8 @@ namespace Bit.Core.Models.Api return new SsoConfigurationData { ConfigType = ConfigType, - UseCryptoAgent = UseCryptoAgent, - CryptoAgentUrl = CryptoAgentUrl, + UseKeyConnector = UseKeyConnector, + KeyConnectorUrl = KeyConnectorUrl, Authority = Authority, ClientId = ClientId, ClientSecret = ClientSecret, diff --git a/src/Core/Models/Api/Request/TwoFactorRequestModels.cs b/src/Core/Models/Api/Request/TwoFactorRequestModels.cs index e6bf6bacc..21c722888 100644 --- a/src/Core/Models/Api/Request/TwoFactorRequestModels.cs +++ b/src/Core/Models/Api/Request/TwoFactorRequestModels.cs @@ -7,7 +7,7 @@ using System.Linq; namespace Bit.Core.Models.Api { - public class UpdateTwoFactorAuthenticatorRequestModel : TwoFactorRequestModel + public class UpdateTwoFactorAuthenticatorRequestModel : SecretVerificationRequestModel { [Required] [StringLength(50)] @@ -38,7 +38,7 @@ namespace Bit.Core.Models.Api } } - public class UpdateTwoFactorDuoRequestModel : TwoFactorRequestModel, IValidatableObject + public class UpdateTwoFactorDuoRequestModel : SecretVerificationRequestModel, IValidatableObject { [Required] [StringLength(50)] @@ -111,7 +111,7 @@ namespace Bit.Core.Models.Api } } - public class UpdateTwoFactorYubicoOtpRequestModel : TwoFactorRequestModel, IValidatableObject + public class UpdateTwoFactorYubicoOtpRequestModel : SecretVerificationRequestModel, IValidatableObject { public string Key1 { get; set; } public string Key2 { get; set; } @@ -195,7 +195,7 @@ namespace Bit.Core.Models.Api } } - public class TwoFactorEmailRequestModel : TwoFactorRequestModel + public class TwoFactorEmailRequestModel : SecretVerificationRequestModel { [Required] [EmailAddress] @@ -231,13 +231,18 @@ namespace Bit.Core.Models.Api public string Name { get; set; } } - public class TwoFactorWebAuthnDeleteRequestModel : TwoFactorRequestModel, IValidatableObject + public class TwoFactorWebAuthnDeleteRequestModel : SecretVerificationRequestModel, IValidatableObject { [Required] public int? Id { get; set; } public IEnumerable Validate(ValidationContext validationContext) { + foreach (var validationResult in Validate(validationContext)) + { + yield return validationResult; + } + if (!Id.HasValue || Id < 0 || Id > 5) { yield return new ValidationResult("Invalid Key Id", new string[] { nameof(Id) }); @@ -252,18 +257,12 @@ namespace Bit.Core.Models.Api public string Token { get; set; } } - public class TwoFactorProviderRequestModel : TwoFactorRequestModel + public class TwoFactorProviderRequestModel : SecretVerificationRequestModel { [Required] public TwoFactorProviderType? Type { get; set; } } - public class TwoFactorRequestModel - { - [Required] - public string MasterPasswordHash { get; set; } - } - public class TwoFactorRecoveryRequestModel : TwoFactorEmailRequestModel { [Required] diff --git a/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs b/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs index 14b0c50e5..414b896af 100644 --- a/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs +++ b/src/Core/Models/Api/Response/OrganizationUserResponseModel.cs @@ -79,6 +79,8 @@ namespace Bit.Core.Models.Api Email = organizationUser.Email; TwoFactorEnabled = twoFactorEnabled; SsoBound = !string.IsNullOrWhiteSpace(organizationUser.SsoExternalId); + // Prevent reset password when using key connector. + ResetPasswordEnrolled = ResetPasswordEnrolled && !organizationUser.UsesKeyConnector; } public string Name { get; set; } diff --git a/src/Core/Models/Api/Response/ProfileOrganizationResponseModel.cs b/src/Core/Models/Api/Response/ProfileOrganizationResponseModel.cs index bd2124fe9..427873b65 100644 --- a/src/Core/Models/Api/Response/ProfileOrganizationResponseModel.cs +++ b/src/Core/Models/Api/Response/ProfileOrganizationResponseModel.cs @@ -38,6 +38,12 @@ namespace Bit.Core.Models.Api UserId = organization.UserId?.ToString(); ProviderId = organization.ProviderId?.ToString(); ProviderName = organization.ProviderName; + if (organization.SsoConfig != null) + { + var ssoConfigData = SsoConfigurationData.Deserialize(organization.SsoConfig); + UsesKeyConnector = ssoConfigData.UseKeyConnector && !string.IsNullOrEmpty(ssoConfigData.KeyConnectorUrl); + KeyConnectorUrl = ssoConfigData.KeyConnectorUrl; + } } public string Id { get; set; } @@ -68,5 +74,7 @@ namespace Bit.Core.Models.Api public bool HasPublicAndPrivateKeys { get; set; } public string ProviderId { get; set; } public string ProviderName { get; set; } + public bool UsesKeyConnector { get; set; } + public string KeyConnectorUrl { get; set; } } } diff --git a/src/Core/Models/Api/Response/ProfileResponseModel.cs b/src/Core/Models/Api/Response/ProfileResponseModel.cs index 531735fe5..16c07650c 100644 --- a/src/Core/Models/Api/Response/ProfileResponseModel.cs +++ b/src/Core/Models/Api/Response/ProfileResponseModel.cs @@ -31,6 +31,7 @@ namespace Bit.Core.Models.Api PrivateKey = user.PrivateKey; SecurityStamp = user.SecurityStamp; ForcePasswordReset = user.ForcePasswordReset; + UsesKeyConnector = user.UsesKeyConnector; Organizations = organizationsUserDetails?.Select(o => new ProfileOrganizationResponseModel(o)); Providers = providerUserDetails?.Select(p => new ProfileProviderResponseModel(p)); ProviderOrganizations = @@ -49,6 +50,7 @@ namespace Bit.Core.Models.Api public string PrivateKey { get; set; } public string SecurityStamp { get; set; } public bool ForcePasswordReset { get; set; } + public bool UsesKeyConnector { get; set; } public IEnumerable Organizations { get; set; } public IEnumerable Providers { get; set; } public IEnumerable ProviderOrganizations { get; set; } diff --git a/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs b/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs index dc9e4dbfa..85f1cc396 100644 --- a/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs +++ b/src/Core/Models/Data/OrganizationUserOrganizationDetails.cs @@ -33,5 +33,6 @@ namespace Bit.Core.Models.Data public string PrivateKey { get; set; } public Guid? ProviderId { get; set; } public string ProviderName { get; set; } + public string SsoConfig { get; set; } } } diff --git a/src/Core/Models/Data/OrganizationUserUserDetails.cs b/src/Core/Models/Data/OrganizationUserUserDetails.cs index d14469e67..55a4f22d8 100644 --- a/src/Core/Models/Data/OrganizationUserUserDetails.cs +++ b/src/Core/Models/Data/OrganizationUserUserDetails.cs @@ -23,6 +23,7 @@ namespace Bit.Core.Models.Data public string SsoExternalId { get; set; } public string Permissions { get; set; } public string ResetPasswordKey { get; set; } + public bool UsesKeyConnector { get; set; } public Dictionary GetTwoFactorProviders() { diff --git a/src/Core/Models/Data/SsoConfigurationData.cs b/src/Core/Models/Data/SsoConfigurationData.cs index 1434ad530..a7f2e3f99 100644 --- a/src/Core/Models/Data/SsoConfigurationData.cs +++ b/src/Core/Models/Data/SsoConfigurationData.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.Json; using Bit.Core.Enums; using Bit.Core.Sso; +using Bit.Core.Utilities; using Microsoft.AspNetCore.Authentication.OpenIdConnect; namespace Bit.Core.Models.Data @@ -13,11 +15,20 @@ namespace Bit.Core.Models.Data private const string _oidcSignedOutPath = "/oidc-signedout"; private const string _saml2ModulePath = "/saml2"; + public static SsoConfigurationData Deserialize(string data) + { + return CoreHelpers.LoadClassFromJsonData(data); + } + + public string Serialize() + { + return CoreHelpers.ClassToJsonData(this); + } + public SsoType ConfigType { get; set; } - // Crypto Agent - public bool UseCryptoAgent { get; set; } - public string CryptoAgentUrl { get; set; } + public bool UseKeyConnector { get; set; } + public string KeyConnectorUrl { get; set; } // OIDC public string Authority { get; set; } diff --git a/src/Core/Models/Table/SsoConfig.cs b/src/Core/Models/Table/SsoConfig.cs index 6a63f2203..4cd1d7155 100644 --- a/src/Core/Models/Table/SsoConfig.cs +++ b/src/Core/Models/Table/SsoConfig.cs @@ -1,5 +1,4 @@ using System; -using System.Text.Json; using Bit.Core.Models.Data; namespace Bit.Core.Models.Table @@ -13,11 +12,6 @@ namespace Bit.Core.Models.Table 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 @@ -26,12 +20,12 @@ namespace Bit.Core.Models.Table public SsoConfigurationData GetData() { - return JsonSerializer.Deserialize(Data, _jsonSerializerOptions); + return SsoConfigurationData.Deserialize(Data); } public void SetData(SsoConfigurationData data) { - Data = JsonSerializer.Serialize(data, _jsonSerializerOptions); + Data = data.Serialize(); } } } diff --git a/src/Core/Models/Table/User.cs b/src/Core/Models/Table/User.cs index 449b911ce..1ad875a1c 100644 --- a/src/Core/Models/Table/User.cs +++ b/src/Core/Models/Table/User.cs @@ -58,7 +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 bool UsesKeyConnector { get; set; } public void SetNewId() { diff --git a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserOrganizationDetailsViewQuery.cs b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserOrganizationDetailsViewQuery.cs index 1fff50851..293a8dfb8 100644 --- a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserOrganizationDetailsViewQuery.cs +++ b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserOrganizationDetailsViewQuery.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Bit.Core.Models.Data; +using Microsoft.Azure.Documents.SystemFunctions; namespace Bit.Core.Repositories.EntityFramework.Queries { @@ -16,8 +17,10 @@ namespace Bit.Core.Repositories.EntityFramework.Queries from po in po_g.DefaultIfEmpty() join p in dbContext.Providers on po.ProviderId equals p.Id into p_g from p in p_g.DefaultIfEmpty() + join ss in dbContext.SsoConfigs on ou.OrganizationId equals ss.OrganizationId into ss_g + from ss in ss_g.DefaultIfEmpty() where ((su == null || !su.OrganizationId.HasValue) || su.OrganizationId == ou.OrganizationId) - select new { ou, o, su, p }; + select new { ou, o, su, p, ss }; return query.Select(x => new OrganizationUserOrganizationDetails { OrganizationId = x.ou.OrganizationId, @@ -48,6 +51,7 @@ namespace Bit.Core.Repositories.EntityFramework.Queries PrivateKey = x.o.PrivateKey, ProviderId = x.p.Id, ProviderName = x.p.Name, + SsoConfig = x.ss.Data, }); } } diff --git a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserUserViewQuery.cs b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserUserViewQuery.cs index 861a334a8..49c8274e7 100644 --- a/src/Core/Repositories/EntityFramework/Queries/OrganizationUserUserViewQuery.cs +++ b/src/Core/Repositories/EntityFramework/Queries/OrganizationUserUserViewQuery.cs @@ -29,6 +29,7 @@ namespace Bit.Core.Repositories.EntityFramework.Queries SsoExternalId = x.su.ExternalId, Permissions = x.ou.Permissions, ResetPasswordKey = x.ou.ResetPasswordKey, + UsesKeyConnector = x.u.UsesKeyConnector, }); } } diff --git a/src/Core/Services/IMailService.cs b/src/Core/Services/IMailService.cs index 8abf5a0d5..433a21bf1 100644 --- a/src/Core/Services/IMailService.cs +++ b/src/Core/Services/IMailService.cs @@ -49,5 +49,6 @@ namespace Bit.Core.Services Task SendProviderConfirmedEmailAsync(string providerName, string email); Task SendProviderUserRemoved(string providerName, string email); Task SendUpdatedTempPasswordEmailAsync(string email, string userName); + Task SendOTPEmailAsync(string email, string token); } } diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index 256575b1c..8b697b6e3 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -34,7 +34,8 @@ 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 SetKeyConnectorKeyAsync(User user, string key, string orgIdentifier); + Task ConvertToKeyConnectorAsync(User user); 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, @@ -74,5 +75,8 @@ namespace Bit.Core.Services Task GenerateSignInTokenAsync(User user, string purpose); Task RotateApiKeyAsync(User user); string GetUserName(ClaimsPrincipal principal); + Task SendOTPAsync(User user); + Task VerifyOTPAsync(User user, string token); + Task VerifySecretAsync(User user, string secret); } } diff --git a/src/Core/Services/Implementations/HandlebarsMailService.cs b/src/Core/Services/Implementations/HandlebarsMailService.cs index 8e981f386..4c6d1ec8f 100644 --- a/src/Core/Services/Implementations/HandlebarsMailService.cs +++ b/src/Core/Services/Implementations/HandlebarsMailService.cs @@ -755,5 +755,20 @@ namespace Bit.Core.Services message.Category = "UpdatedTempPassword"; await _mailDeliveryService.SendEmailAsync(message); } + + public async Task SendOTPEmailAsync(string email, string token) + { + var message = CreateDefaultMessage("Your Bitwarden Verification Code", email); + var model = new EmailTokenViewModel + { + Token = token, + WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash, + SiteName = _globalSettings.SiteName, + }; + await AddMessageContentAsync(message, "OTPEmail", model); + message.MetaData.Add("SendGridBypassListManagement", true); + message.Category = "OTP"; + await _mailDeliveryService.SendEmailAsync(message); + } } } diff --git a/src/Core/Services/Implementations/OrganizationService.cs b/src/Core/Services/Implementations/OrganizationService.cs index aeba331c8..b775eb4bf 100644 --- a/src/Core/Services/Implementations/OrganizationService.cs +++ b/src/Core/Services/Implementations/OrganizationService.cs @@ -908,6 +908,8 @@ namespace Bit.Core.Services public async Task DeleteAsync(Organization organization) { + await ValidateDeleteOrganizationAsync(organization); + if (!string.IsNullOrWhiteSpace(organization.GatewaySubscriptionId)) { try @@ -2135,5 +2137,14 @@ namespace Bit.Core.Services throw new BadRequestException("Custom users can not manage Admins or Owners."); } } + + private async Task ValidateDeleteOrganizationAsync(Organization organization) + { + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(organization.Id); + if (ssoConfig?.GetData()?.UseKeyConnector == true) + { + throw new BadRequestException("You cannot delete an Organization that is using Key Connector."); + } + } } } diff --git a/src/Core/Services/Implementations/PolicyService.cs b/src/Core/Services/Implementations/PolicyService.cs index 960c3ce79..c38ea9fde 100644 --- a/src/Core/Services/Implementations/PolicyService.cs +++ b/src/Core/Services/Implementations/PolicyService.cs @@ -15,6 +15,7 @@ namespace Bit.Core.Services private readonly IOrganizationRepository _organizationRepository; private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IPolicyRepository _policyRepository; + private readonly ISsoConfigRepository _ssoConfigRepository; private readonly IMailService _mailService; public PolicyService( @@ -22,12 +23,14 @@ namespace Bit.Core.Services IOrganizationRepository organizationRepository, IOrganizationUserRepository organizationUserRepository, IPolicyRepository policyRepository, + ISsoConfigRepository ssoConfigRepository, IMailService mailService) { _eventService = eventService; _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; _policyRepository = policyRepository; + _ssoConfigRepository = ssoConfigRepository; _mailService = mailService; } @@ -64,6 +67,12 @@ namespace Bit.Core.Services { throw new BadRequestException("Maximum Vault Timeout policy is enabled."); } + + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(org.Id); + if (ssoConfig?.GetData()?.UseKeyConnector == true) + { + throw new BadRequestException("KeyConnector is enabled."); + } } break; diff --git a/src/Core/Services/Implementations/SsoConfigService.cs b/src/Core/Services/Implementations/SsoConfigService.cs index 714d9df4d..c219d954c 100644 --- a/src/Core/Services/Implementations/SsoConfigService.cs +++ b/src/Core/Services/Implementations/SsoConfigService.cs @@ -1,5 +1,7 @@ using System; using System.Threading.Tasks; +using Bit.Core.Enums; +using Bit.Core.Exceptions; using Bit.Core.Models.Table; using Bit.Core.Repositories; @@ -8,11 +10,20 @@ namespace Bit.Core.Services public class SsoConfigService : ISsoConfigService { private readonly ISsoConfigRepository _ssoConfigRepository; + private readonly IPolicyRepository _policyRepository; + private readonly IOrganizationRepository _organizationRepository; + private readonly IEventService _eventService; public SsoConfigService( - ISsoConfigRepository ssoConfigRepository) + ISsoConfigRepository ssoConfigRepository, + IPolicyRepository policyRepository, + IOrganizationRepository organizationRepository, + IEventService eventService) { _ssoConfigRepository = ssoConfigRepository; + _policyRepository = policyRepository; + _organizationRepository = organizationRepository; + _eventService = eventService; } public async Task SaveAsync(SsoConfig config) @@ -23,7 +34,49 @@ namespace Bit.Core.Services { config.CreationDate = now; } + + var useKeyConnector = config.GetData().UseKeyConnector; + if (useKeyConnector) + { + await VerifyDependenciesAsync(config); + } + + var oldConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(config.OrganizationId); + if (oldConfig?.GetData()?.UseKeyConnector == true && !useKeyConnector) + { + throw new BadRequestException("KeyConnector cannot be disabled at this moment."); + } + + await LogEventsAsync(config, oldConfig); await _ssoConfigRepository.UpsertAsync(config); } + + private async Task VerifyDependenciesAsync(SsoConfig config) + { + var policy = await _policyRepository.GetByOrganizationIdTypeAsync(config.OrganizationId, PolicyType.SingleOrg); + if (policy is not { Enabled: true }) + { + throw new BadRequestException("KeyConnector requires Single Organization to be enabled."); + } + } + + private async Task LogEventsAsync(SsoConfig config, SsoConfig oldConfig) + { + var organization = await _organizationRepository.GetByIdAsync(config.OrganizationId); + if (oldConfig?.Enabled != config.Enabled) + { + var e = config.Enabled ? EventType.Organization_EnabledSso : EventType.Organization_DisabledSso; + await _eventService.LogOrganizationEventAsync(organization, e); + } + + var useKeyConnector = config.GetData().UseKeyConnector; + if (oldConfig?.GetData()?.UseKeyConnector != useKeyConnector) + { + var e = useKeyConnector + ? EventType.Organization_EnabledKeyConnector + : EventType.Organization_DisabledKeyConnector; + await _eventService.LogOrganizationEventAsync(organization, e); + } + } } } diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 5e1cc14e7..215e89585 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -1,26 +1,27 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; +using Bit.Core.Context; +using Bit.Core.Enums; +using Bit.Core.Exceptions; +using Bit.Core.Models; +using Bit.Core.Models.Business; +using Bit.Core.Models.Table; +using Bit.Core.Repositories; +using Bit.Core.Settings; +using Bit.Core.Utilities; +using Fido2NetLib; +using Fido2NetLib.Objects; +using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Bit.Core.Models.Table; -using Bit.Core.Repositories; -using System.Linq; -using Bit.Core.Enums; -using System.Security.Claims; -using Bit.Core.Models; -using Bit.Core.Models.Business; -using U2fLib = U2F.Core.Crypto.U2F; -using Bit.Core.Context; -using Bit.Core.Exceptions; -using Bit.Core.Utilities; -using Bit.Core.Settings; -using System.IO; using Newtonsoft.Json; -using Microsoft.AspNetCore.DataProtection; -using Fido2NetLib; -using Fido2NetLib.Objects; +using File = System.IO.File; +using U2fLib = U2F.Core.Crypto.U2F; namespace Bit.Core.Services { @@ -602,7 +603,7 @@ namespace Bit.Core.Services return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch()); } - public async Task SetPasswordAsync(User user, string masterPassword, string key, + public async Task SetPasswordAsync(User user, string masterPassword, string key, string orgIdentifier = null) { if (user == null) @@ -627,42 +628,63 @@ namespace Bit.Core.Services await _userRepository.ReplaceAsync(user); await _eventService.LogUserEventAsync(user.Id, EventType.User_ChangedPassword); - + if (!string.IsNullOrWhiteSpace(orgIdentifier)) { await _organizationService.AcceptUserAsync(orgIdentifier, user, this); } - + return IdentityResult.Success; } - public async Task SetCryptoAgentKeyAsync(User user, string key, string orgIdentifier) + public async Task SetKeyConnectorKeyAsync(User user, string key, string orgIdentifier) { if (user == null) { throw new ArgumentNullException(nameof(user)); } - if (user.UsesCryptoAgent) + if (user.UsesKeyConnector) { - Logger.LogWarning("Already uses crypto agent."); + Logger.LogWarning("Already uses key connector."); return IdentityResult.Failed(_identityErrorDescriber.UserAlreadyHasPassword()); } user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow; user.Key = key; - user.UsesCryptoAgent = true; + user.UsesKeyConnector = true; await _userRepository.ReplaceAsync(user); - // TODO: Use correct event - await _eventService.LogUserEventAsync(user.Id, EventType.User_ChangedPassword); + await _eventService.LogUserEventAsync(user.Id, EventType.User_MigratedKeyToKeyConnector); await _organizationService.AcceptUserAsync(orgIdentifier, user, this); return IdentityResult.Success; } - + public async Task ConvertToKeyConnectorAsync(User user) + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + if (user.UsesKeyConnector) + { + Logger.LogWarning("Already uses key connector."); + return IdentityResult.Failed(_identityErrorDescriber.UserAlreadyHasPassword()); + } + + user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow; + user.MasterPassword = null; + user.UsesKeyConnector = true; + + await _userRepository.ReplaceAsync(user); + await _eventService.LogUserEventAsync(user.Id, EventType.User_MigratedKeyToKeyConnector); + + return IdentityResult.Success; + } + public async Task AdminResetPasswordAsync(OrganizationUserType callingUserType, Guid orgId, Guid id, string newMasterPassword, string key) { // Org must be able to use reset password @@ -671,15 +693,15 @@ namespace Bit.Core.Services { throw new BadRequestException("Organization does not allow password reset."); } - - // Enterprise policy must be enabled + + // Enterprise policy must be enabled var resetPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword); if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled) { throw new BadRequestException("Organization does not have the password reset policy enabled."); } - + // Org User must be confirmed and have a ResetPasswordKey var orgUser = await _organizationUserRepository.GetByIdAsync(id); if (orgUser == null || orgUser.Status != OrganizationUserStatusType.Confirmed || @@ -688,7 +710,7 @@ namespace Bit.Core.Services { throw new BadRequestException("Organization User not valid"); } - + // Calling User must be of higher/equal user type to reset user's password var canAdjustPassword = false; switch (callingUserType) @@ -715,7 +737,12 @@ namespace Bit.Core.Services { throw new NotFoundException(); } - + + if (user.UsesKeyConnector) + { + throw new BadRequestException("Cannot reset password of a user with key connector."); + } + var result = await UpdatePasswordHash(user, newMasterPassword); if (!result.Succeeded) { @@ -733,14 +760,14 @@ namespace Bit.Core.Services return IdentityResult.Success; } - + public async Task UpdateTempPasswordAsync(User user, string newMasterPassword, string key, string hint) { if (!user.ForcePasswordReset) { throw new BadRequestException("User does not have a temporary password to update."); } - + var result = await UpdatePasswordHash(user, newMasterPassword); if (!result.Succeeded) { @@ -820,14 +847,14 @@ namespace Bit.Core.Services return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch()); } - public async Task RefreshSecurityStampAsync(User user, string masterPassword) + public async Task RefreshSecurityStampAsync(User user, string secret) { if (user == null) { throw new ArgumentNullException(nameof(user)); } - if (await CheckPasswordAsync(user, masterPassword)) + if (await VerifySecretAsync(user, secret)) { var result = await base.UpdateSecurityStampAsync(user); if (!result.Succeeded) @@ -878,7 +905,7 @@ namespace Bit.Core.Services } } - public async Task RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode, + public async Task RecoverTwoFactorAsync(string email, string secret, string recoveryCode, IOrganizationService organizationService) { var user = await _userRepository.GetByEmailAsync(email); @@ -888,7 +915,7 @@ namespace Bit.Core.Services return false; } - if (!await CheckPasswordAsync(user, masterPassword)) + if (!await VerifySecretAsync(user, secret)) { return false; } @@ -1253,7 +1280,7 @@ namespace Bit.Core.Services purpose); return token; } - + private async Task UpdatePasswordHash(User user, string newPassword, bool validatePassword = true, bool refreshStamp = true) { @@ -1350,5 +1377,35 @@ namespace Bit.Core.Services user.RevisionDate = DateTime.UtcNow; await _userRepository.ReplaceAsync(user); } + + public async Task SendOTPAsync(User user) + { + if (user.Email == null) + { + throw new BadRequestException("No user email."); + } + + if (!user.UsesKeyConnector) + { + throw new BadRequestException("Not using key connector."); + } + + var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultEmailProvider, + "otp:" + user.Email); + await _mailService.SendOTPEmailAsync(user.Email, token); + } + + public Task VerifyOTPAsync(User user, string token) + { + return base.VerifyUserTokenAsync(user, TokenOptions.DefaultEmailProvider, + "otp:" + user.Email, token); + } + + public async Task VerifySecretAsync(User user, string secret) + { + return user.UsesKeyConnector + ? await VerifyOTPAsync(user, secret) + : await CheckPasswordAsync(user, secret); + } } } diff --git a/src/Core/Services/NoopImplementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs index f514993d1..230469931 100644 --- a/src/Core/Services/NoopImplementations/NoopMailService.cs +++ b/src/Core/Services/NoopImplementations/NoopMailService.cs @@ -200,5 +200,10 @@ namespace Bit.Core.Services { return Task.FromResult(0); } + + public Task SendOTPEmailAsync(string email, string token) + { + return Task.FromResult(0); + } } } diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index 5f7154d53..a9f6b7765 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -895,6 +895,16 @@ namespace Bit.Core.Utilities return System.Text.Json.JsonSerializer.Deserialize(jsonData, options); } + public static string ClassToJsonData(T data) + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + + return System.Text.Json.JsonSerializer.Serialize(data, options); + } + public static ICollection AddIfNotExists(this ICollection list, T item) { if (list.Contains(item)) diff --git a/src/Core/Utilities/ServiceCollectionExtensions.cs b/src/Core/Utilities/ServiceCollectionExtensions.cs index 04c6a2eeb..1c4718111 100644 --- a/src/Core/Utilities/ServiceCollectionExtensions.cs +++ b/src/Core/Utilities/ServiceCollectionExtensions.cs @@ -175,7 +175,7 @@ namespace Bit.Core.Utilities services.AddScoped(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddScoped(); services.AddScoped(); } diff --git a/src/Sql/dbo/Stored Procedures/User_Create.sql b/src/Sql/dbo/Stored Procedures/User_Create.sql index 882a63283..87931c8a0 100644 --- a/src/Sql/dbo/Stored Procedures/User_Create.sql +++ b/src/Sql/dbo/Stored Procedures/User_Create.sql @@ -31,7 +31,7 @@ @RevisionDate DATETIME2(7), @ApiKey VARCHAR(30), @ForcePasswordReset BIT = 0, - @UsesCryptoAgent BIT = 0 + @UsesKeyConnector BIT = 0 AS BEGIN SET NOCOUNT ON @@ -70,7 +70,7 @@ BEGIN [RevisionDate], [ApiKey], [ForcePasswordReset], - [UsesCryptoAgent] + [UsesKeyConnector] ) VALUES ( @@ -106,6 +106,6 @@ BEGIN @RevisionDate, @ApiKey, @ForcePasswordReset, - @UsesCryptoAgent + @UsesKeyConnector ) END diff --git a/src/Sql/dbo/Stored Procedures/User_Update.sql b/src/Sql/dbo/Stored Procedures/User_Update.sql index d851ce433..669d2c3ac 100644 --- a/src/Sql/dbo/Stored Procedures/User_Update.sql +++ b/src/Sql/dbo/Stored Procedures/User_Update.sql @@ -31,7 +31,7 @@ @RevisionDate DATETIME2(7), @ApiKey VARCHAR(30), @ForcePasswordReset BIT = 0, - @UsesCryptoAgent BIT = 0 + @UsesKeyConnector BIT = 0 AS BEGIN SET NOCOUNT ON @@ -70,7 +70,7 @@ BEGIN [RevisionDate] = @RevisionDate, [ApiKey] = @ApiKey, [ForcePasswordReset] = @ForcePasswordReset, - [UsesCryptoAgent] = @UsesCryptoAgent + [UsesKeyConnector] = @UsesKeyConnector WHERE [Id] = @Id END diff --git a/src/Sql/dbo/Tables/User.sql b/src/Sql/dbo/Tables/User.sql index b6c9a6dc2..f58b4b555 100644 --- a/src/Sql/dbo/Tables/User.sql +++ b/src/Sql/dbo/Tables/User.sql @@ -31,7 +31,7 @@ [RevisionDate] DATETIME2 (7) NOT NULL, [ApiKey] VARCHAR (30) NOT NULL, [ForcePasswordReset] BIT NOT NULL, - [UsesCryptoAgent] BIT NOT NULL, + [UsesKeyConnector] BIT NOT NULL, CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC) ); diff --git a/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql b/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql index 3f27be2a4..c65c3f328 100644 --- a/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql +++ b/src/Sql/dbo/Views/OrganizationUserOrganizationDetailsView.sql @@ -29,7 +29,8 @@ SELECT SU.[ExternalId] SsoExternalId, OU.[Permissions], PO.[ProviderId], - P.[Name] ProviderName + P.[Name] ProviderName, + SS.[Data] SsoConfig FROM [dbo].[OrganizationUser] OU INNER JOIN @@ -40,3 +41,5 @@ LEFT JOIN [dbo].[ProviderOrganization] PO ON PO.[OrganizationId] = O.[Id] LEFT JOIN [dbo].[Provider] P ON P.[Id] = PO.[ProviderId] +LEFT JOIN + [dbo].[SsoConfig] SS ON SS.[OrganizationId] = OU.[OrganizationId] diff --git a/src/Sql/dbo/Views/OrganizationUserUserDetailsView.sql b/src/Sql/dbo/Views/OrganizationUserUserDetailsView.sql index 7232721a4..e13097884 100644 --- a/src/Sql/dbo/Views/OrganizationUserUserDetailsView.sql +++ b/src/Sql/dbo/Views/OrganizationUserUserDetailsView.sql @@ -14,7 +14,8 @@ SELECT OU.[ExternalId], SU.[ExternalId] SsoExternalId, OU.[Permissions], - OU.[ResetPasswordKey] + OU.[ResetPasswordKey], + U.[UsesKeyConnector] FROM [dbo].[OrganizationUser] OU LEFT JOIN diff --git a/test/Api.Test/Controllers/AccountsControllerTests.cs b/test/Api.Test/Controllers/AccountsControllerTests.cs index d2c47b08c..a64fddc4f 100644 --- a/test/Api.Test/Controllers/AccountsControllerTests.cs +++ b/test/Api.Test/Controllers/AccountsControllerTests.cs @@ -55,7 +55,6 @@ namespace Bit.Api.Test.Controllers _organizationUserRepository, _providerUserRepository, _paymentService, - _ssoUserRepository, _userRepository, _userService, _sendRepository, @@ -320,7 +319,7 @@ namespace Bit.Api.Test.Controllers var user = GenerateExampleUser(); ConfigureUserServiceToReturnValidPrincipalFor(user); ConfigureUserServiceToAcceptPasswordFor(user); - await _sut.ApiKey(new ApiKeyRequestModel()); + await _sut.ApiKey(new SecretVerificationRequestModel()); } [Fact] @@ -329,7 +328,7 @@ namespace Bit.Api.Test.Controllers ConfigureUserServiceToReturnNullPrincipal(); await Assert.ThrowsAsync( - () => _sut.ApiKey(new ApiKeyRequestModel()) + () => _sut.ApiKey(new SecretVerificationRequestModel()) ); } @@ -340,7 +339,7 @@ namespace Bit.Api.Test.Controllers ConfigureUserServiceToReturnValidPrincipalFor(user); ConfigureUserServiceToRejectPasswordFor(user); await Assert.ThrowsAsync( - () => _sut.ApiKey(new ApiKeyRequestModel()) + () => _sut.ApiKey(new SecretVerificationRequestModel()) ); } @@ -350,7 +349,7 @@ namespace Bit.Api.Test.Controllers var user = GenerateExampleUser(); ConfigureUserServiceToReturnValidPrincipalFor(user); ConfigureUserServiceToAcceptPasswordFor(user); - await _sut.RotateApiKey(new ApiKeyRequestModel()); + await _sut.RotateApiKey(new SecretVerificationRequestModel()); } [Fact] @@ -359,7 +358,7 @@ namespace Bit.Api.Test.Controllers ConfigureUserServiceToReturnNullPrincipal(); await Assert.ThrowsAsync( - () => _sut.ApiKey(new ApiKeyRequestModel()) + () => _sut.ApiKey(new SecretVerificationRequestModel()) ); } @@ -370,7 +369,7 @@ namespace Bit.Api.Test.Controllers ConfigureUserServiceToReturnValidPrincipalFor(user); ConfigureUserServiceToRejectPasswordFor(user); await Assert.ThrowsAsync( - () => _sut.ApiKey(new ApiKeyRequestModel()) + () => _sut.ApiKey(new SecretVerificationRequestModel()) ); } @@ -409,6 +408,8 @@ namespace Bit.Api.Test.Controllers { _userService.CheckPasswordAsync(user, Arg.Any()) .Returns(Task.FromResult(true)); + _userService.VerifySecretAsync(user, Arg.Any()) + .Returns(Task.FromResult(true)); } private void ConfigureUserServiceToReturnValidIdFor(User user) diff --git a/test/Core.Test/Services/OrganizationServiceTests.cs b/test/Core.Test/Services/OrganizationServiceTests.cs index c9e74b93f..8a505a1cd 100644 --- a/test/Core.Test/Services/OrganizationServiceTests.cs +++ b/test/Core.Test/Services/OrganizationServiceTests.cs @@ -891,5 +891,38 @@ namespace Bit.Core.Test.Services Assert.False(result); Assert.Contains("Cannot autoscale on self-hosted instance", failureMessage); } + + [Theory, PaidOrganizationAutoData] + public async Task Delete_Success(Organization organization, SutProvider sutProvider) + { + var organizationRepository = sutProvider.GetDependency(); + var applicationCacheService = sutProvider.GetDependency(); + + await sutProvider.Sut.DeleteAsync(organization); + + await organizationRepository.Received().DeleteAsync(organization); + await applicationCacheService.Received().DeleteOrganizationAbilityAsync(organization.Id); + } + + [Theory, PaidOrganizationAutoData] + public async Task Delete_Fails_KeyConnector(Organization organization, SutProvider sutProvider, + SsoConfig ssoConfig) + { + ssoConfig.Enabled = true; + ssoConfig.SetData(new SsoConfigurationData { UseKeyConnector = true }); + var ssoConfigRepository = sutProvider.GetDependency(); + var organizationRepository = sutProvider.GetDependency(); + var applicationCacheService = sutProvider.GetDependency(); + + ssoConfigRepository.GetByOrganizationIdAsync(organization.Id).Returns(ssoConfig); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.DeleteAsync(organization)); + + Assert.Contains("You cannot delete an Organization that is using Key Connector.", exception.Message); + + await organizationRepository.DidNotReceiveWithAnyArgs().DeleteAsync(default); + await applicationCacheService.DidNotReceiveWithAnyArgs().DeleteOrganizationAbilityAsync(default); + } } } diff --git a/test/Core.Test/Services/PolicyServiceTests.cs b/test/Core.Test/Services/PolicyServiceTests.cs index e1fd3f7c5..18d68e82b 100644 --- a/test/Core.Test/Services/PolicyServiceTests.cs +++ b/test/Core.Test/Services/PolicyServiceTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Bit.Core.Exceptions; +using Bit.Core.Models.Data; using Bit.Core.Models.Table; using Bit.Core.Repositories; using Bit.Core.Services; @@ -125,6 +126,40 @@ namespace Bit.Core.Test.Services .UpsertAsync(default); } + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task SaveAsync_SingleOrg_KeyConnectorEnabled_ThrowsBadRequest( + [PolicyFixtures.Policy(Enums.PolicyType.SingleOrg)] Core.Models.Table.Policy policy, + SutProvider sutProvider) + { + policy.Enabled = false; + + SetupOrg(sutProvider, policy.OrganizationId, new Organization + { + Id = policy.OrganizationId, + UsePolicies = true, + }); + + var ssoConfig = new SsoConfig { Enabled = true }; + var data = new SsoConfigurationData { UseKeyConnector = true }; + ssoConfig.SetData(data); + + sutProvider.GetDependency() + .GetByOrganizationIdAsync(policy.OrganizationId) + .Returns(ssoConfig); + + var badRequestException = await Assert.ThrowsAsync( + () => sutProvider.Sut.SaveAsync(policy, + Substitute.For(), + Substitute.For(), + Guid.NewGuid())); + + Assert.Contains("KeyConnector is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase); + + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .UpsertAsync(default); + } + [Theory, CustomAutoData(typeof(SutProviderCustomization))] public async Task SaveAsync_RequireSsoPolicy_NotEnabled_ThrowsBadRequestAsync([PolicyFixtures.Policy(Enums.PolicyType.RequireSso)] Core.Models.Table.Policy policy, SutProvider sutProvider) { diff --git a/test/Core.Test/Services/SsoConfigServiceTests.cs b/test/Core.Test/Services/SsoConfigServiceTests.cs index 403df8dac..f148b37a0 100644 --- a/test/Core.Test/Services/SsoConfigServiceTests.cs +++ b/test/Core.Test/Services/SsoConfigServiceTests.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Bit.Core.Exceptions; using Bit.Core.Models.Table; using Bit.Core.Repositories; using Bit.Core.Services; @@ -21,7 +22,7 @@ namespace Bit.Core.Test.Services var ssoConfig = new SsoConfig { Id = 1, - Data = "TESTDATA", + Data = "{}", Enabled = true, OrganizationId = Guid.NewGuid(), CreationDate = utcNow.AddDays(-10), @@ -48,7 +49,7 @@ namespace Bit.Core.Test.Services var ssoConfig = new SsoConfig { Id = default, - Data = "TESTDATA", + Data = "{}", Enabled = true, OrganizationId = Guid.NewGuid(), CreationDate = utcNow.AddDays(-10), @@ -66,5 +67,67 @@ namespace Bit.Core.Test.Services Assert.True(ssoConfig.CreationDate - utcNow < TimeSpan.FromSeconds(1)); Assert.True(ssoConfig.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task SaveAsync_PreventDisablingKeyConnector(SutProvider sutProvider, Guid orgId) + { + var utcNow = DateTime.UtcNow; + + var oldSsoConfig = new SsoConfig + { + Id = 1, + Data = "{\"useKeyConnector\": true}", + Enabled = true, + OrganizationId = orgId, + CreationDate = utcNow.AddDays(-10), + RevisionDate = utcNow.AddDays(-10), + }; + + var newSsoConfig = new SsoConfig + { + Id = 1, + Data = "{}", + Enabled = true, + OrganizationId = orgId, + CreationDate = utcNow.AddDays(-10), + RevisionDate = utcNow, + }; + + var ssoConfigRepository = sutProvider.GetDependency(); + ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(oldSsoConfig); + ssoConfigRepository.UpsertAsync(newSsoConfig).Returns(Task.CompletedTask); + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.SaveAsync(newSsoConfig)); + + Assert.Contains("KeyConnector cannot be disabled at this moment.", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .UpsertAsync(default); + } + + [Theory, CustomAutoData(typeof(SutProviderCustomization))] + public async Task SaveAsync_KeyConnector_SingleOrgNotEnabled(SutProvider sutProvider) + { + var utcNow = DateTime.UtcNow; + + var ssoConfig = new SsoConfig + { + Id = default, + Data = "{\"useKeyConnector\": true}", + Enabled = true, + OrganizationId = Guid.NewGuid(), + CreationDate = utcNow.AddDays(-10), + RevisionDate = utcNow.AddDays(-10), + }; + + var exception = await Assert.ThrowsAsync( + () => sutProvider.Sut.SaveAsync(ssoConfig)); + + Assert.Contains("KeyConnector requires Single Organization to be enabled.", exception.Message); + + await sutProvider.GetDependency().DidNotReceiveWithAnyArgs() + .UpsertAsync(default); + } } } diff --git a/util/Migrator/DbScripts/2021-10-13_00_CryptoAgent.sql b/util/Migrator/DbScripts/2021-11-08_00_KeyConnector.sql similarity index 70% rename from util/Migrator/DbScripts/2021-10-13_00_CryptoAgent.sql rename to util/Migrator/DbScripts/2021-11-08_00_KeyConnector.sql index 5d43d0ddd..3daae34d0 100644 --- a/util/Migrator/DbScripts/2021-10-13_00_CryptoAgent.sql +++ b/util/Migrator/DbScripts/2021-11-08_00_KeyConnector.sql @@ -1,24 +1,24 @@ -IF COL_LENGTH('[dbo].[User]', 'UsesCryptoAgent') IS NULL +IF COL_LENGTH('[dbo].[User]', 'UsesKeyConnector') IS NULL BEGIN ALTER TABLE [dbo].[User] ADD - [UsesCryptoAgent] BIT NULL + [UsesKeyConnector] BIT NULL END GO UPDATE [dbo].[User] SET - [UsesCryptoAgent] = 0 + [UsesKeyConnector] = 0 WHERE - [UsesCryptoAgent] IS NULL + [UsesKeyConnector] IS NULL GO ALTER TABLE [dbo].[User] ALTER COLUMN - [UsesCryptoAgent] BIT NOT NULL + [UsesKeyConnector] BIT NOT NULL GO -- View: User @@ -75,7 +75,7 @@ CREATE PROCEDURE [dbo].[User_Create] @RevisionDate DATETIME2(7), @ApiKey VARCHAR(30), @ForcePasswordReset BIT = 0, - @UsesCryptoAgent BIT = 0 + @UsesKeyConnector BIT = 0 AS BEGIN SET NOCOUNT ON @@ -114,7 +114,7 @@ BEGIN [RevisionDate], [ApiKey], [ForcePasswordReset], - [UsesCryptoAgent] + [UsesKeyConnector] ) VALUES ( @@ -150,7 +150,7 @@ BEGIN @RevisionDate, @ApiKey, @ForcePasswordReset, - @UsesCryptoAgent + @UsesKeyConnector ) END GO @@ -194,7 +194,7 @@ CREATE PROCEDURE [dbo].[User_Update] @RevisionDate DATETIME2(7), @ApiKey VARCHAR(30), @ForcePasswordReset BIT = 0, - @UsesCryptoAgent BIT = 0 + @UsesKeyConnector BIT = 0 AS BEGIN SET NOCOUNT ON @@ -233,7 +233,92 @@ BEGIN [RevisionDate] = @RevisionDate, [ApiKey] = @ApiKey, [ForcePasswordReset] = @ForcePasswordReset, - [UsesCryptoAgent] = @UsesCryptoAgent + [UsesKeyConnector] = @UsesKeyConnector WHERE [Id] = @Id END +GO + +IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationUserOrganizationDetailsView') +BEGIN + DROP VIEW [dbo].[OrganizationUserOrganizationDetailsView] +END +GO + +CREATE VIEW [dbo].[OrganizationUserOrganizationDetailsView] +AS +SELECT + OU.[UserId], + OU.[OrganizationId], + O.[Name], + O.[Enabled], + O.[UsePolicies], + O.[UseSso], + O.[UseGroups], + O.[UseDirectory], + O.[UseEvents], + O.[UseTotp], + O.[Use2fa], + O.[UseApi], + O.[UseResetPassword], + O.[SelfHost], + O.[UsersGetPremium], + O.[Seats], + O.[MaxCollections], + O.[MaxStorageGb], + O.[Identifier], + OU.[Key], + OU.[ResetPasswordKey], + O.[PublicKey], + O.[PrivateKey], + OU.[Status], + OU.[Type], + SU.[ExternalId] SsoExternalId, + OU.[Permissions], + PO.[ProviderId], + P.[Name] ProviderName, + SS.[Data] SsoConfig +FROM + [dbo].[OrganizationUser] OU + INNER JOIN + [dbo].[Organization] O ON O.[Id] = OU.[OrganizationId] + LEFT JOIN + [dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId] + LEFT JOIN + [dbo].[ProviderOrganization] PO ON PO.[OrganizationId] = O.[Id] + LEFT JOIN + [dbo].[Provider] P ON P.[Id] = PO.[ProviderId] + LEFT JOIN + [dbo].[SsoConfig] SS ON SS.[OrganizationId] = OU.[OrganizationId] +GO + +IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'OrganizationUserUserDetailsView') + BEGIN + DROP VIEW [dbo].[OrganizationUserUserDetailsView] + END +GO + +CREATE VIEW [dbo].[OrganizationUserUserDetailsView] +AS +SELECT + OU.[Id], + OU.[UserId], + OU.[OrganizationId], + U.[Name], + ISNULL(U.[Email], OU.[Email]) Email, + U.[TwoFactorProviders], + U.[Premium], + OU.[Status], + OU.[Type], + OU.[AccessAll], + OU.[ExternalId], + SU.[ExternalId] SsoExternalId, + OU.[Permissions], + OU.[ResetPasswordKey], + U.[UsesKeyConnector] +FROM + [dbo].[OrganizationUser] OU +LEFT JOIN + [dbo].[User] U ON U.[Id] = OU.[UserId] +LEFT JOIN + [dbo].[SsoUser] SU ON SU.[UserId] = OU.[UserId] AND SU.[OrganizationId] = OU.[OrganizationId] diff --git a/util/MySqlMigrations/Migrations/20211108041911_KeyConnector.Designer.cs b/util/MySqlMigrations/Migrations/20211108041911_KeyConnector.Designer.cs new file mode 100644 index 000000000..2852a1190 --- /dev/null +++ b/util/MySqlMigrations/Migrations/20211108041911_KeyConnector.Designer.cs @@ -0,0 +1,1495 @@ +// +using System; +using Bit.Core.Repositories.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace Bit.MySqlMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20211108041911_KeyConnector")] + partial class KeyConnector + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 64) + .HasAnnotation("ProductVersion", "5.0.9"); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Attachments") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletedDate") + .HasColumnType("datetime(6)"); + + b.Property("Favorites") + .HasColumnType("longtext"); + + b.Property("Folders") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Reprompt") + .HasColumnType("tinyint unsigned"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("HidePasswords") + .HasColumnType("tinyint(1)"); + + b.Property("ReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("GranteeId") + .HasColumnType("char(36)"); + + b.Property("GrantorId") + .HasColumnType("char(36)"); + + b.Property("KeyEncrypted") + .HasColumnType("longtext"); + + b.Property("LastNotificationDate") + .HasColumnType("datetime(6)"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("WaitTimeDays") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Event", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ActingUserId") + .HasColumnType("char(36)"); + + b.Property("CipherId") + .HasColumnType("char(36)"); + + b.Property("CollectionId") + .HasColumnType("char(36)"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("DeviceType") + .HasColumnType("tinyint unsigned"); + + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("PolicyId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("ProviderOrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderUserId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Event"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ConsumedDate") + .HasColumnType("datetime(6)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("char(36)"); + + b.Property("OrganizationUserId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Installation", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("int"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("datetime(6)"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PlanType") + .HasColumnType("tinyint unsigned"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Seats") + .HasColumnType("int"); + + b.Property("SelfHost") + .HasColumnType("tinyint(1)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("Use2fa") + .HasColumnType("tinyint(1)"); + + b.Property("UseApi") + .HasColumnType("tinyint(1)"); + + b.Property("UseDirectory") + .HasColumnType("tinyint(1)"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.Property("UseGroups") + .HasColumnType("tinyint(1)"); + + b.Property("UsePolicies") + .HasColumnType("tinyint(1)"); + + b.Property("UseResetPassword") + .HasColumnType("tinyint(1)"); + + b.Property("UseSso") + .HasColumnType("tinyint(1)"); + + b.Property("UseTotp") + .HasColumnType("tinyint(1)"); + + b.Property("UsersGetPremium") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessAll") + .HasColumnType("tinyint(1)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ResetPasswordKey") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("BillingEmail") + .HasColumnType("longtext"); + + b.Property("BusinessAddress1") + .HasColumnType("longtext"); + + b.Property("BusinessAddress2") + .HasColumnType("longtext"); + + b.Property("BusinessAddress3") + .HasColumnType("longtext"); + + b.Property("BusinessCountry") + .HasColumnType("longtext"); + + b.Property("BusinessName") + .HasColumnType("longtext"); + + b.Property("BusinessTaxNumber") + .HasColumnType("longtext"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .HasColumnType("longtext"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("UseEvents") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Settings") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("Permissions") + .HasColumnType("longtext"); + + b.Property("ProviderId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccessCount") + .HasColumnType("int"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("DeletionDate") + .HasColumnType("datetime(6)"); + + b.Property("Disabled") + .HasColumnType("tinyint(1)"); + + b.Property("ExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("HideEmail") + .HasColumnType("tinyint(1)"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("MaxAccessCount") + .HasColumnType("int"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("longtext"); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Active") + .HasColumnType("tinyint(1)"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Rate") + .HasColumnType("decimal(65,30)"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("varchar(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("Amount") + .HasColumnType("decimal(65,30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("OrganizationId") + .HasColumnType("char(36)"); + + b.Property("PaymentMethodType") + .HasColumnType("tinyint unsigned"); + + b.Property("Refunded") + .HasColumnType("tinyint(1)"); + + b.Property("RefundedAmount") + .HasColumnType("decimal(65,30)"); + + b.Property("Type") + .HasColumnType("tinyint unsigned"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AppId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Challenge") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("KeyHandle") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("Version") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("U2f"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Property("Id") + .HasColumnType("char(36)"); + + b.Property("AccountRevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreationDate") + .HasColumnType("datetime(6)"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailVerified") + .HasColumnType("tinyint(1)"); + + b.Property("EquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("longtext"); + + b.Property("ForcePasswordReset") + .HasColumnType("tinyint(1)"); + + b.Property("Gateway") + .HasColumnType("tinyint unsigned"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Kdf") + .HasColumnType("tinyint unsigned"); + + b.Property("KdfIterations") + .HasColumnType("int"); + + b.Property("Key") + .HasColumnType("longtext"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Premium") + .HasColumnType("tinyint(1)"); + + b.Property("PremiumExpirationDate") + .HasColumnType("datetime(6)"); + + b.Property("PrivateKey") + .HasColumnType("longtext"); + + b.Property("PublicKey") + .HasColumnType("longtext"); + + b.Property("ReferenceData") + .HasColumnType("longtext"); + + b.Property("RenewalReminderDate") + .HasColumnType("datetime(6)"); + + b.Property("RevisionDate") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("longtext"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("varchar(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("U2fs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + + b.Navigation("U2fs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/MySqlMigrations/Migrations/20211108041911_KeyConnector.cs b/util/MySqlMigrations/Migrations/20211108041911_KeyConnector.cs new file mode 100644 index 000000000..3e48440fa --- /dev/null +++ b/util/MySqlMigrations/Migrations/20211108041911_KeyConnector.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Bit.MySqlMigrations.Migrations +{ + public partial class KeyConnector : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UsesKeyConnector", + table: "User", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UsesKeyConnector", + table: "User"); + } + } +} diff --git a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs index 9bc3ba7f3..ce4df31bd 100644 --- a/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/MySqlMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -1127,6 +1127,9 @@ namespace Bit.MySqlMigrations.Migrations .HasMaxLength(32) .HasColumnType("varchar(32)"); + b.Property("UsesKeyConnector") + .HasColumnType("tinyint(1)"); + b.HasKey("Id"); b.ToTable("User"); diff --git a/util/MySqlMigrations/Scripts/2021-11-08_00_KeyConnector.sql b/util/MySqlMigrations/Scripts/2021-11-08_00_KeyConnector.sql new file mode 100644 index 000000000..3fdcbb1c5 --- /dev/null +++ b/util/MySqlMigrations/Scripts/2021-11-08_00_KeyConnector.sql @@ -0,0 +1,9 @@ +START TRANSACTION; + +ALTER TABLE `User` ADD `UsesKeyConnector` tinyint(1) NOT NULL DEFAULT FALSE; + +INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`) +VALUES ('20211108041911_KeyConnector', '5.0.9'); + +COMMIT; + diff --git a/util/PostgresMigrations/Migrations/20211108041547_KeyConnector.Designer.cs b/util/PostgresMigrations/Migrations/20211108041547_KeyConnector.Designer.cs new file mode 100644 index 000000000..8919e604e --- /dev/null +++ b/util/PostgresMigrations/Migrations/20211108041547_KeyConnector.Designer.cs @@ -0,0 +1,1504 @@ +// +using System; +using Bit.Core.Repositories.EntityFramework; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Bit.PostgresMigrations.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20211108041547_KeyConnector")] + partial class KeyConnector + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:CollationDefinition:postgresIndetermanisticCollation", "en-u-ks-primary,en-u-ks-primary,icu,False") + .HasAnnotation("Relational:MaxIdentifierLength", 63) + .HasAnnotation("ProductVersion", "5.0.9") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Attachments") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletedDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Favorites") + .HasColumnType("text"); + + b.Property("Folders") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Reprompt") + .HasColumnType("smallint"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Cipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "CipherId"); + + b.HasIndex("CipherId"); + + b.ToTable("CollectionCipher"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.HasKey("CollectionId", "GroupId"); + + b.HasIndex("GroupId"); + + b.ToTable("CollectionGroups"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("HidePasswords") + .HasColumnType("boolean"); + + b.Property("ReadOnly") + .HasColumnType("boolean"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("CollectionId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PushToken") + .HasMaxLength(255) + .HasColumnType("character varying(255)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Device"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("GranteeId") + .HasColumnType("uuid"); + + b.Property("GrantorId") + .HasColumnType("uuid"); + + b.Property("KeyEncrypted") + .HasColumnType("text"); + + b.Property("LastNotificationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RecoveryInitiatedDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("WaitTimeDays") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GranteeId"); + + b.HasIndex("GrantorId"); + + b.ToTable("EmergencyAccess"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Event", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ActingUserId") + .HasColumnType("uuid"); + + b.Property("CipherId") + .HasColumnType("uuid"); + + b.Property("CollectionId") + .HasColumnType("uuid"); + + b.Property("Date") + .HasColumnType("timestamp without time zone"); + + b.Property("DeviceType") + .HasColumnType("smallint"); + + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("IpAddress") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("PolicyId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("ProviderOrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderUserId") + .HasColumnType("uuid"); + + b.Property("Type") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.ToTable("Event"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("Folder"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Grant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ClientId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ConsumedDate") + .HasColumnType("timestamp without time zone"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Type") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Key"); + + b.ToTable("Grant"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.Property("GroupId") + .HasColumnType("uuid"); + + b.Property("OrganizationUserId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("GroupId", "OrganizationUserId"); + + b.HasIndex("OrganizationUserId"); + + b.HasIndex("UserId"); + + b.ToTable("GroupUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Installation", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Key") + .HasMaxLength(150) + .HasColumnType("character varying(150)"); + + b.HasKey("Id"); + + b.ToTable("Installation"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("ApiKey") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("BillingEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("BusinessAddress1") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress2") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessAddress3") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessCountry") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.Property("BusinessName") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BusinessTaxNumber") + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Identifier") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MaxAutoscaleSeats") + .HasColumnType("integer"); + + b.Property("MaxCollections") + .HasColumnType("smallint"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OwnersNotifiedOfAutoscaling") + .HasColumnType("timestamp without time zone"); + + b.Property("Plan") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PlanType") + .HasColumnType("smallint"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Seats") + .HasColumnType("integer"); + + b.Property("SelfHost") + .HasColumnType("boolean"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("Use2fa") + .HasColumnType("boolean"); + + b.Property("UseApi") + .HasColumnType("boolean"); + + b.Property("UseDirectory") + .HasColumnType("boolean"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.Property("UseGroups") + .HasColumnType("boolean"); + + b.Property("UsePolicies") + .HasColumnType("boolean"); + + b.Property("UseResetPassword") + .HasColumnType("boolean"); + + b.Property("UseSso") + .HasColumnType("boolean"); + + b.Property("UseTotp") + .HasColumnType("boolean"); + + b.Property("UsersGetPremium") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessAll") + .HasColumnType("boolean"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("ExternalId") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ResetPasswordKey") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("Policy"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.Provider", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("BillingEmail") + .HasColumnType("text"); + + b.Property("BusinessAddress1") + .HasColumnType("text"); + + b.Property("BusinessAddress2") + .HasColumnType("text"); + + b.Property("BusinessAddress3") + .HasColumnType("text"); + + b.Property("BusinessCountry") + .HasColumnType("text"); + + b.Property("BusinessName") + .HasColumnType("text"); + + b.Property("BusinessTaxNumber") + .HasColumnType("text"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("UseEvents") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Settings") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("ProviderId"); + + b.ToTable("ProviderOrganization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("Permissions") + .HasColumnType("text"); + + b.Property("ProviderId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId"); + + b.HasIndex("UserId"); + + b.ToTable("ProviderUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccessCount") + .HasColumnType("integer"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("DeletionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("ExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("HideEmail") + .HasColumnType("boolean"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("MaxAccessCount") + .HasColumnType("integer"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("Password") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Send"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Data") + .HasColumnType("text"); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.ToTable("SsoConfig"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ExternalId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("SsoUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.TaxRate", b => + { + b.Property("Id") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Active") + .HasColumnType("boolean"); + + b.Property("Country") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("PostalCode") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Rate") + .HasColumnType("numeric"); + + b.Property("State") + .HasMaxLength(2) + .HasColumnType("character varying(2)"); + + b.HasKey("Id"); + + b.ToTable("TaxRate"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Amount") + .HasColumnType("numeric"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Details") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OrganizationId") + .HasColumnType("uuid"); + + b.Property("PaymentMethodType") + .HasColumnType("smallint"); + + b.Property("Refunded") + .HasColumnType("boolean"); + + b.Property("RefundedAmount") + .HasColumnType("numeric"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("OrganizationId"); + + b.HasIndex("UserId"); + + b.ToTable("Transaction"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AppId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Challenge") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("KeyHandle") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("Version") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("U2f"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("AccountRevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("ApiKey") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("character varying(30)"); + + b.Property("CreationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("Culture") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .UseCollation("postgresIndetermanisticCollation"); + + b.Property("EmailVerified") + .HasColumnType("boolean"); + + b.Property("EquivalentDomains") + .HasColumnType("text"); + + b.Property("ExcludedGlobalEquivalentDomains") + .HasColumnType("text"); + + b.Property("ForcePasswordReset") + .HasColumnType("boolean"); + + b.Property("Gateway") + .HasColumnType("smallint"); + + b.Property("GatewayCustomerId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("GatewaySubscriptionId") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Kdf") + .HasColumnType("smallint"); + + b.Property("KdfIterations") + .HasColumnType("integer"); + + b.Property("Key") + .HasColumnType("text"); + + b.Property("LicenseKey") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("MasterPassword") + .HasMaxLength(300) + .HasColumnType("character varying(300)"); + + b.Property("MasterPasswordHint") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("MaxStorageGb") + .HasColumnType("smallint"); + + b.Property("Name") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Premium") + .HasColumnType("boolean"); + + b.Property("PremiumExpirationDate") + .HasColumnType("timestamp without time zone"); + + b.Property("PrivateKey") + .HasColumnType("text"); + + b.Property("PublicKey") + .HasColumnType("text"); + + b.Property("ReferenceData") + .HasColumnType("text"); + + b.Property("RenewalReminderDate") + .HasColumnType("timestamp without time zone"); + + b.Property("RevisionDate") + .HasColumnType("timestamp without time zone"); + + b.Property("SecurityStamp") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Storage") + .HasColumnType("bigint"); + + b.Property("TwoFactorProviders") + .HasColumnType("text"); + + b.Property("TwoFactorRecoveryCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Ciphers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Ciphers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionCipher", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Cipher", "Cipher") + .WithMany("CollectionCiphers") + .HasForeignKey("CipherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionCiphers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Cipher"); + + b.Navigation("Collection"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionGroup", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionGroups") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany() + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Collection"); + + b.Navigation("Group"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.CollectionUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Collection", "Collection") + .WithMany("CollectionUsers") + .HasForeignKey("CollectionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany("CollectionUsers") + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("CollectionUsers") + .HasForeignKey("UserId"); + + b.Navigation("Collection"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Device", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.EmergencyAccess", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantee") + .WithMany() + .HasForeignKey("GranteeId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "Grantor") + .WithMany() + .HasForeignKey("GrantorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Grantee"); + + b.Navigation("Grantor"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Folder", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Folders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Groups") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.GroupUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Group", "Group") + .WithMany("GroupUsers") + .HasForeignKey("GroupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.OrganizationUser", "OrganizationUser") + .WithMany() + .HasForeignKey("OrganizationUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", null) + .WithMany("GroupUsers") + .HasForeignKey("UserId"); + + b.Navigation("Group"); + + b.Navigation("OrganizationUser"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("OrganizationUsers") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("OrganizationUsers") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Policy", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Policies") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderOrganization", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("Provider"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Provider.ProviderUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Provider.Provider", "Provider") + .WithMany() + .HasForeignKey("ProviderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Provider"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Send", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany() + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoConfig", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoConfigs") + .HasForeignKey("OrganizationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.SsoUser", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("SsoUsers") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("SsoUsers") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Transaction", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.Organization", "Organization") + .WithMany("Transactions") + .HasForeignKey("OrganizationId"); + + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("Transactions") + .HasForeignKey("UserId"); + + b.Navigation("Organization"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.U2f", b => + { + b.HasOne("Bit.Core.Models.EntityFramework.User", "User") + .WithMany("U2fs") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Cipher", b => + { + b.Navigation("CollectionCiphers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Collection", b => + { + b.Navigation("CollectionCiphers"); + + b.Navigation("CollectionGroups"); + + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Group", b => + { + b.Navigation("GroupUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.Organization", b => + { + b.Navigation("Ciphers"); + + b.Navigation("Groups"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("Policies"); + + b.Navigation("SsoConfigs"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.OrganizationUser", b => + { + b.Navigation("CollectionUsers"); + }); + + modelBuilder.Entity("Bit.Core.Models.EntityFramework.User", b => + { + b.Navigation("Ciphers"); + + b.Navigation("CollectionUsers"); + + b.Navigation("Folders"); + + b.Navigation("GroupUsers"); + + b.Navigation("OrganizationUsers"); + + b.Navigation("SsoUsers"); + + b.Navigation("Transactions"); + + b.Navigation("U2fs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/util/PostgresMigrations/Migrations/20211108041547_KeyConnector.cs b/util/PostgresMigrations/Migrations/20211108041547_KeyConnector.cs new file mode 100644 index 000000000..264869124 --- /dev/null +++ b/util/PostgresMigrations/Migrations/20211108041547_KeyConnector.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Bit.PostgresMigrations.Migrations +{ + public partial class KeyConnector : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UsesKeyConnector", + table: "User", + type: "boolean", + nullable: false, + defaultValue: false); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UsesKeyConnector", + table: "User"); + } + } +} diff --git a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs index 859380ad5..51d27d539 100644 --- a/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs +++ b/util/PostgresMigrations/Migrations/DatabaseContextModelSnapshot.cs @@ -1136,6 +1136,9 @@ namespace Bit.PostgresMigrations.Migrations .HasMaxLength(32) .HasColumnType("character varying(32)"); + b.Property("UsesKeyConnector") + .HasColumnType("boolean"); + b.HasKey("Id"); b.ToTable("User"); diff --git a/util/PostgresMigrations/Scripts/2021-11-08_00_KeyConnector.psql b/util/PostgresMigrations/Scripts/2021-11-08_00_KeyConnector.psql new file mode 100644 index 000000000..cdfca0e11 --- /dev/null +++ b/util/PostgresMigrations/Scripts/2021-11-08_00_KeyConnector.psql @@ -0,0 +1,9 @@ +START TRANSACTION; + +ALTER TABLE "User" ADD "UsesKeyConnector" boolean NOT NULL DEFAULT FALSE; + +INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion") +VALUES ('20211108041547_KeyConnector', '5.0.9'); + +COMMIT; +