1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-01 16:12:49 -05:00

Merge remote-tracking branch 'origin/master' into ac/ac-1638/disallow-secrets-manager-for-msp-managed-organizations

This commit is contained in:
Thomas Rittson
2023-10-04 14:29:38 +10:00
53 changed files with 7156 additions and 86 deletions

View File

@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Install cloc - name: Install cloc
run: | run: |
@ -34,7 +34,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Set up dotnet - name: Set up dotnet
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
@ -49,7 +49,7 @@ jobs:
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Set up dotnet - name: Set up dotnet
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
@ -135,7 +135,7 @@ jobs:
node: true node: true
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Set up dotnet - name: Set up dotnet
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
@ -246,7 +246,7 @@ jobs:
dotnet: true dotnet: true
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Check Branch to Publish - name: Check Branch to Publish
env: env:
@ -277,7 +277,7 @@ jobs:
- name: Retrieve github PAT secrets - name: Retrieve github PAT secrets
id: retrieve-secret-pat id: retrieve-secret-pat
uses: bitwarden/gh-actions/get-keyvault-secrets@f096207b7a2f31723165aee6ad03e91716686e78 uses: bitwarden/gh-actions/get-keyvault-secrets@fdcf1fcec3b04762ce48216cbf3af32498bed74c
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"
secrets: "github-pat-bitwarden-devops-bot-repo-scope" secrets: "github-pat-bitwarden-devops-bot-repo-scope"
@ -337,7 +337,7 @@ jobs:
needs: build-docker needs: build-docker
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Set up dotnet - name: Set up dotnet
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
@ -476,7 +476,7 @@ jobs:
- win-x64 - win-x64
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Set up dotnet - name: Set up dotnet
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
@ -528,7 +528,7 @@ jobs:
- name: Retrieve github PAT secrets - name: Retrieve github PAT secrets
id: retrieve-secret-pat id: retrieve-secret-pat
uses: bitwarden/gh-actions/get-keyvault-secrets@f096207b7a2f31723165aee6ad03e91716686e78 uses: bitwarden/gh-actions/get-keyvault-secrets@fdcf1fcec3b04762ce48216cbf3af32498bed74c
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"
secrets: "github-pat-bitwarden-devops-bot-repo-scope" secrets: "github-pat-bitwarden-devops-bot-repo-scope"
@ -603,7 +603,7 @@ jobs:
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@f096207b7a2f31723165aee6ad03e91716686e78 uses: bitwarden/gh-actions/get-keyvault-secrets@fdcf1fcec3b04762ce48216cbf3af32498bed74c
if: failure() if: failure()
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"

View File

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
########## ACR ########## ########## ACR ##########
- name: Login to Azure - QA Subscription - name: Login to Azure - QA Subscription

View File

@ -92,7 +92,7 @@ jobs:
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@f096207b7a2f31723165aee6ad03e91716686e78 uses: bitwarden/gh-actions/get-keyvault-secrets@fdcf1fcec3b04762ce48216cbf3af32498bed74c
if: failure() if: failure()
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"

View File

@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Set up dotnet - name: Set up dotnet
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0

View File

@ -38,7 +38,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Set up dotnet - name: Set up dotnet
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0 uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0

View File

@ -30,7 +30,7 @@ jobs:
label: "DB-migrations-changed" label: "DB-migrations-changed"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
with: with:
fetch-depth: 2 fetch-depth: 2

View File

@ -37,11 +37,11 @@ jobs:
fi fi
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Check Release Version - name: Check Release Version
id: version id: version
uses: bitwarden/gh-actions/release-version-check@f096207b7a2f31723165aee6ad03e91716686e78 uses: bitwarden/gh-actions/release-version-check@fdcf1fcec3b04762ce48216cbf3af32498bed74c
with: with:
release-type: ${{ github.event.inputs.release_type }} release-type: ${{ github.event.inputs.release_type }}
project-type: dotnet project-type: dotnet
@ -89,7 +89,7 @@ jobs:
- name: Download latest Release ${{ matrix.name }} asset - name: Download latest Release ${{ matrix.name }} asset
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@bc3bf31f1d9cac9c9d02cae01fc615fa25d38929 uses: bitwarden/gh-actions/download-artifacts@fdcf1fcec3b04762ce48216cbf3af32498bed74c
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@ -98,7 +98,7 @@ jobs:
- name: Dry Run - Download latest Release ${{ matrix.name }} asset - name: Dry Run - Download latest Release ${{ matrix.name }} asset
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@bc3bf31f1d9cac9c9d02cae01fc615fa25d38929 uses: bitwarden/gh-actions/download-artifacts@fdcf1fcec3b04762ce48216cbf3af32498bed74c
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@ -211,7 +211,7 @@ jobs:
echo "Github Release Option: $RELEASE_OPTION" echo "Github Release Option: $RELEASE_OPTION"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Setup project name - name: Setup project name
id: setup id: setup
@ -274,7 +274,7 @@ jobs:
steps: steps:
- name: Download latest Release Docker Stubs - name: Download latest Release Docker Stubs
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@bc3bf31f1d9cac9c9d02cae01fc615fa25d38929 uses: bitwarden/gh-actions/download-artifacts@fdcf1fcec3b04762ce48216cbf3af32498bed74c
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@ -287,7 +287,7 @@ jobs:
- name: Dry Run - Download latest Release Docker Stubs - name: Dry Run - Download latest Release Docker Stubs
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@bc3bf31f1d9cac9c9d02cae01fc615fa25d38929 uses: bitwarden/gh-actions/download-artifacts@fdcf1fcec3b04762ce48216cbf3af32498bed74c
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout Branch - name: Checkout Branch
uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0 uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
- name: Login to Azure - CI Subscription - name: Login to Azure - CI Subscription
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7 uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
@ -23,7 +23,7 @@ jobs:
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@f096207b7a2f31723165aee6ad03e91716686e78 uses: bitwarden/gh-actions/get-keyvault-secrets@fdcf1fcec3b04762ce48216cbf3af32498bed74c
with: with:
keyvault: "bitwarden-ci" keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase" secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
@ -40,7 +40,7 @@ jobs:
run: git switch -c version_bump_${{ github.event.inputs.version_number }} run: git switch -c version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Props - name: Bump Version - Props
uses: bitwarden/gh-actions/version-bump@f096207b7a2f31723165aee6ad03e91716686e78 uses: bitwarden/gh-actions/version-bump@fdcf1fcec3b04762ce48216cbf3af32498bed74c
with: with:
version: ${{ github.event.inputs.version_number }} version: ${{ github.event.inputs.version_number }}
file_path: "Directory.Build.props" file_path: "Directory.Build.props"

View File

@ -8,4 +8,4 @@ on:
jobs: jobs:
call-workflow: call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@f096207b7a2f31723165aee6ad03e91716686e78 uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@fdcf1fcec3b04762ce48216cbf3af32498bed74c

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>2023.9.0</Version> <Version>2023.9.1</Version>
<RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace> <RootNamespace>Bit.$(MSBuildProjectName)</RootNamespace>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile> <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>

View File

@ -19,6 +19,7 @@ using Bit.Core.Utilities;
using Bit.Core.Vault.Repositories; using Bit.Core.Vault.Repositories;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Stripe;
namespace Bit.Admin.Controllers; namespace Bit.Admin.Controllers;
@ -47,6 +48,7 @@ public class OrganizationsController : Controller
private readonly ISecretRepository _secretRepository; private readonly ISecretRepository _secretRepository;
private readonly IProjectRepository _projectRepository; private readonly IProjectRepository _projectRepository;
private readonly IServiceAccountRepository _serviceAccountRepository; private readonly IServiceAccountRepository _serviceAccountRepository;
private readonly IStripeSyncService _stripeSyncService;
public OrganizationsController( public OrganizationsController(
IOrganizationService organizationService, IOrganizationService organizationService,
@ -70,7 +72,8 @@ public class OrganizationsController : Controller
ICurrentContext currentContext, ICurrentContext currentContext,
ISecretRepository secretRepository, ISecretRepository secretRepository,
IProjectRepository projectRepository, IProjectRepository projectRepository,
IServiceAccountRepository serviceAccountRepository) IServiceAccountRepository serviceAccountRepository,
IStripeSyncService stripeSyncService)
{ {
_organizationService = organizationService; _organizationService = organizationService;
_organizationRepository = organizationRepository; _organizationRepository = organizationRepository;
@ -94,6 +97,7 @@ public class OrganizationsController : Controller
_secretRepository = secretRepository; _secretRepository = secretRepository;
_projectRepository = projectRepository; _projectRepository = projectRepository;
_serviceAccountRepository = serviceAccountRepository; _serviceAccountRepository = serviceAccountRepository;
_stripeSyncService = stripeSyncService;
} }
[RequirePermission(Permission.Org_List_View)] [RequirePermission(Permission.Org_List_View)]
@ -208,6 +212,16 @@ public class OrganizationsController : Controller
throw new BadRequestException("Plan does not support Secrets Manager"); throw new BadRequestException("Plan does not support Secrets Manager");
} }
try
{
await _stripeSyncService.UpdateCustomerEmailAddress(organization.GatewayCustomerId, organization.BillingEmail);
}
catch (StripeException stripeException)
{
_logger.LogError(stripeException, "Failed to update billing email address in Stripe for Organization with ID '{organizationId}'", organization.Id);
throw;
}
await _organizationRepository.ReplaceAsync(organization); await _organizationRepository.ReplaceAsync(organization);
await _applicationCacheService.UpsertOrganizationAbilityAsync(organization); await _applicationCacheService.UpsertOrganizationAbilityAsync(organization);
await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.OrganizationEditedByAdmin, organization, _currentContext) await _referenceEventService.RaiseEventAsync(new ReferenceEvent(ReferenceEventType.OrganizationEditedByAdmin, organization, _currentContext)
@ -215,6 +229,7 @@ public class OrganizationsController : Controller
EventRaisedByUser = _userService.GetUserName(User), EventRaisedByUser = _userService.GetUserName(User),
SalesAssistedTrialStarted = model.SalesAssistedTrialStarted, SalesAssistedTrialStarted = model.SalesAssistedTrialStarted,
}); });
return RedirectToAction("Edit", new { id }); return RedirectToAction("Edit", new { id });
} }

View File

@ -277,7 +277,13 @@
} }
else else
{ {
<input type="email" class="form-control" asp-for="BillingEmail" readonly='@(!canEditBilling)'> <input
type="text"
class="form-control"
asp-for="BillingEmail"
readonly='@(!canEditBilling)'
pattern="@(@"[^@\s]+@[^@\s]+\.[^@\s]+")"
title="Email address must be in the format 'address@domain.com'.">
} }
</div> </div>
</div> </div>

View File

@ -116,6 +116,7 @@ public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
{ {
Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null; Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null;
UpcomingInvoice = subscription.UpcomingInvoice != null ? new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null; UpcomingInvoice = subscription.UpcomingInvoice != null ? new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null;
Discount = subscription.Discount != null ? new BillingCustomerDiscount(subscription.Discount) : null;
Expiration = DateTime.UtcNow.AddYears(1); // Not used, so just give it a value. Expiration = DateTime.UtcNow.AddYears(1); // Not used, so just give it a value.
if (hideSensitiveData) if (hideSensitiveData)
@ -146,6 +147,7 @@ public class OrganizationSubscriptionResponseModel : OrganizationResponseModel
public string StorageName { get; set; } public string StorageName { get; set; }
public double? StorageGb { get; set; } public double? StorageGb { get; set; }
public BillingCustomerDiscount Discount { get; set; }
public BillingSubscription Subscription { get; set; } public BillingSubscription Subscription { get; set; }
public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; } public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; }

View File

@ -14,6 +14,7 @@ public class SubscriptionResponseModel : ResponseModel
Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null; Subscription = subscription.Subscription != null ? new BillingSubscription(subscription.Subscription) : null;
UpcomingInvoice = subscription.UpcomingInvoice != null ? UpcomingInvoice = subscription.UpcomingInvoice != null ?
new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null; new BillingSubscriptionUpcomingInvoice(subscription.UpcomingInvoice) : null;
Discount = subscription.Discount != null ? new BillingCustomerDiscount(subscription.Discount) : null;
StorageName = user.Storage.HasValue ? CoreHelpers.ReadableBytesSize(user.Storage.Value) : null; StorageName = user.Storage.HasValue ? CoreHelpers.ReadableBytesSize(user.Storage.Value) : null;
StorageGb = user.Storage.HasValue ? Math.Round(user.Storage.Value / 1073741824D, 2) : 0; // 1 GB StorageGb = user.Storage.HasValue ? Math.Round(user.Storage.Value / 1073741824D, 2) : 0; // 1 GB
MaxStorageGb = user.MaxStorageGb; MaxStorageGb = user.MaxStorageGb;
@ -41,11 +42,24 @@ public class SubscriptionResponseModel : ResponseModel
public short? MaxStorageGb { get; set; } public short? MaxStorageGb { get; set; }
public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; } public BillingSubscriptionUpcomingInvoice UpcomingInvoice { get; set; }
public BillingSubscription Subscription { get; set; } public BillingSubscription Subscription { get; set; }
public BillingCustomerDiscount Discount { get; set; }
public UserLicense License { get; set; } public UserLicense License { get; set; }
public DateTime? Expiration { get; set; } public DateTime? Expiration { get; set; }
public bool UsingInAppPurchase { get; set; } public bool UsingInAppPurchase { get; set; }
} }
public class BillingCustomerDiscount
{
public BillingCustomerDiscount(SubscriptionInfo.BillingCustomerDiscount discount)
{
Id = discount.Id;
Active = discount.Active;
}
public string Id { get; set; }
public bool Active { get; set; }
}
public class BillingSubscription public class BillingSubscription
{ {
public BillingSubscription(SubscriptionInfo.BillingSubscription sub) public BillingSubscription(SubscriptionInfo.BillingSubscription sub)

View File

@ -36,6 +36,7 @@ public class CiphersController : Controller
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly ILogger<CiphersController> _logger; private readonly ILogger<CiphersController> _logger;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly Version _cipherKeyEncryptionMinimumVersion = new Version(Constants.CipherKeyEncryptionMinimumVersion);
public CiphersController( public CiphersController(
ICipherRepository cipherRepository, ICipherRepository cipherRepository,
@ -177,6 +178,8 @@ public class CiphersController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
ValidateItemLevelEncryptionIsAvailable(cipher);
var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id)).Select(c => c.CollectionId).ToList(); var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id)).Select(c => c.CollectionId).ToList();
var modelOrgId = string.IsNullOrWhiteSpace(model.OrganizationId) ? var modelOrgId = string.IsNullOrWhiteSpace(model.OrganizationId) ?
(Guid?)null : new Guid(model.OrganizationId); (Guid?)null : new Guid(model.OrganizationId);
@ -198,6 +201,9 @@ public class CiphersController : Controller
{ {
var userId = _userService.GetProperUserId(User).Value; var userId = _userService.GetProperUserId(User).Value;
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(id); var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(id);
ValidateItemLevelEncryptionIsAvailable(cipher);
if (cipher == null || !cipher.OrganizationId.HasValue || if (cipher == null || !cipher.OrganizationId.HasValue ||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value)) !await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))
{ {
@ -576,6 +582,8 @@ public class CiphersController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
ValidateItemLevelEncryptionIsAvailable(cipher);
if (request.FileSize > CipherService.MAX_FILE_SIZE) if (request.FileSize > CipherService.MAX_FILE_SIZE)
{ {
throw new BadRequestException($"Max file size is {CipherService.MAX_FILE_SIZE_READABLE}."); throw new BadRequestException($"Max file size is {CipherService.MAX_FILE_SIZE_READABLE}.");
@ -795,4 +803,12 @@ public class CiphersController : Controller
throw new BadRequestException("Invalid content."); throw new BadRequestException("Invalid content.");
} }
} }
private void ValidateItemLevelEncryptionIsAvailable(Cipher cipher)
{
if (cipher.Key != null && _currentContext.ClientVersion < _cipherKeyEncryptionMinimumVersion)
{
throw new BadRequestException("Cannot edit item. Update to the latest version of Bitwarden and try again.");
}
}
} }

View File

@ -18,6 +18,7 @@ public class CipherRequestModel
public string FolderId { get; set; } public string FolderId { get; set; }
public bool Favorite { get; set; } public bool Favorite { get; set; }
public CipherRepromptType Reprompt { get; set; } public CipherRepromptType Reprompt { get; set; }
public string Key { get; set; }
[Required] [Required]
[EncryptedString] [EncryptedString]
[EncryptedStringLength(1000)] [EncryptedStringLength(1000)]
@ -86,6 +87,7 @@ public class CipherRequestModel
} }
existingCipher.Reprompt = Reprompt; existingCipher.Reprompt = Reprompt;
existingCipher.Key = Key;
var hasAttachments2 = (Attachments2?.Count ?? 0) > 0; var hasAttachments2 = (Attachments2?.Count ?? 0) > 0;
var hasAttachments = (Attachments?.Count ?? 0) > 0; var hasAttachments = (Attachments?.Count ?? 0) > 0;

View File

@ -63,6 +63,7 @@ public class CipherMiniResponseModel : ResponseModel
CreationDate = cipher.CreationDate; CreationDate = cipher.CreationDate;
DeletedDate = cipher.DeletedDate; DeletedDate = cipher.DeletedDate;
Reprompt = cipher.Reprompt.GetValueOrDefault(CipherRepromptType.None); Reprompt = cipher.Reprompt.GetValueOrDefault(CipherRepromptType.None);
Key = cipher.Key;
} }
public Guid Id { get; set; } public Guid Id { get; set; }
@ -83,6 +84,7 @@ public class CipherMiniResponseModel : ResponseModel
public DateTime CreationDate { get; set; } public DateTime CreationDate { get; set; }
public DateTime? DeletedDate { get; set; } public DateTime? DeletedDate { get; set; }
public CipherRepromptType Reprompt { get; set; } public CipherRepromptType Reprompt { get; set; }
public string Key { get; set; }
} }
public class CipherResponseModel : CipherMiniResponseModel public class CipherResponseModel : CipherMiniResponseModel

View File

@ -19,6 +19,8 @@ public static class Constants
/// their subscription has expired. /// their subscription has expired.
/// </summary> /// </summary>
public const int OrganizationSelfHostSubscriptionGracePeriodDays = 60; public const int OrganizationSelfHostSubscriptionGracePeriodDays = 60;
public const string CipherKeyEncryptionMinimumVersion = "2023.9.2";
} }
public static class TokenPurposes public static class TokenPurposes
@ -37,6 +39,7 @@ public static class FeatureFlagKeys
public const string DisplayLowKdfIterationWarning = "display-kdf-iteration-warning"; public const string DisplayLowKdfIterationWarning = "display-kdf-iteration-warning";
public const string TrustedDeviceEncryption = "trusted-device-encryption"; public const string TrustedDeviceEncryption = "trusted-device-encryption";
public const string AutofillV2 = "autofill-v2"; public const string AutofillV2 = "autofill-v2";
public const string BrowserFilelessImport = "browser-fileless-import";
public static List<string> GetAllKeys() public static List<string> GetAllKeys()
{ {

View File

@ -5,10 +5,25 @@ namespace Bit.Core.Models.Business;
public class SubscriptionInfo public class SubscriptionInfo
{ {
public BillingCustomerDiscount Discount { get; set; }
public BillingSubscription Subscription { get; set; } public BillingSubscription Subscription { get; set; }
public BillingUpcomingInvoice UpcomingInvoice { get; set; } public BillingUpcomingInvoice UpcomingInvoice { get; set; }
public bool UsingInAppPurchase { get; set; } public bool UsingInAppPurchase { get; set; }
public class BillingCustomerDiscount
{
public BillingCustomerDiscount() { }
public BillingCustomerDiscount(Discount discount)
{
Id = discount.Id;
Active = discount.Start != null && discount.End == null;
}
public string Id { get; }
public bool Active { get; }
}
public class BillingSubscription public class BillingSubscription
{ {
public BillingSubscription(Subscription sub) public BillingSubscription(Subscription sub)

View File

@ -1,5 +1,6 @@
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Settings; using Bit.Core.Settings;
using LaunchDarkly.Logging;
using LaunchDarkly.Sdk.Server; using LaunchDarkly.Sdk.Server;
using LaunchDarkly.Sdk.Server.Integrations; using LaunchDarkly.Sdk.Server.Integrations;
@ -14,6 +15,7 @@ public class LaunchDarklyFeatureService : IFeatureService, IDisposable
IGlobalSettings globalSettings) IGlobalSettings globalSettings)
{ {
var ldConfig = Configuration.Builder(globalSettings.LaunchDarkly?.SdkKey); var ldConfig = Configuration.Builder(globalSettings.LaunchDarkly?.SdkKey);
ldConfig.Logging(Components.Logging().Level(LogLevel.Error));
if (string.IsNullOrEmpty(globalSettings.LaunchDarkly?.SdkKey)) if (string.IsNullOrEmpty(globalSettings.LaunchDarkly?.SdkKey))
{ {

View File

@ -1557,10 +1557,19 @@ public class StripePaymentService : IPaymentService
{ {
var subscriptionInfo = new SubscriptionInfo(); var subscriptionInfo = new SubscriptionInfo();
if (subscriber.IsUser() && !string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId)) if (!string.IsNullOrWhiteSpace(subscriber.GatewayCustomerId))
{ {
var customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId); var customer = await _stripeAdapter.CustomerGetAsync(subscriber.GatewayCustomerId);
subscriptionInfo.UsingInAppPurchase = customer.Metadata.ContainsKey("appleReceipt");
if (customer.Discount != null)
{
subscriptionInfo.Discount = new SubscriptionInfo.BillingCustomerDiscount(customer.Discount);
}
if (subscriber.IsUser())
{
subscriptionInfo.UsingInAppPurchase = customer.Metadata.ContainsKey("appleReceipt");
}
} }
if (!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId)) if (!string.IsNullOrWhiteSpace(subscriber.GatewaySubscriptionId))

View File

@ -21,6 +21,7 @@ public class Cipher : ITableObject<Guid>, ICloneable
public DateTime RevisionDate { get; set; } = DateTime.UtcNow; public DateTime RevisionDate { get; set; } = DateTime.UtcNow;
public DateTime? DeletedDate { get; set; } public DateTime? DeletedDate { get; set; }
public Enums.CipherRepromptType? Reprompt { get; set; } public Enums.CipherRepromptType? Reprompt { get; set; }
public string Key { get; set; }
public void SetNewId() public void SetNewId()
{ {

View File

@ -392,7 +392,8 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
SET SET
[Data] = TC.[Data], [Data] = TC.[Data],
[Attachments] = TC.[Attachments], [Attachments] = TC.[Attachments],
[RevisionDate] = TC.[RevisionDate] [RevisionDate] = TC.[RevisionDate],
[Key] = TC.[Key]
FROM FROM
[dbo].[Cipher] C [dbo].[Cipher] C
INNER JOIN INNER JOIN
@ -506,7 +507,8 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
[Data] = TC.[Data], [Data] = TC.[Data],
[Attachments] = TC.[Attachments], [Attachments] = TC.[Attachments],
[RevisionDate] = TC.[RevisionDate], [RevisionDate] = TC.[RevisionDate],
[DeletedDate] = TC.[DeletedDate] [DeletedDate] = TC.[DeletedDate],
[Key] = TC.[Key]
FROM FROM
[dbo].[Cipher] C [dbo].[Cipher] C
INNER JOIN INNER JOIN
@ -728,6 +730,8 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
ciphersTable.Columns.Add(deletedDateColumn); ciphersTable.Columns.Add(deletedDateColumn);
var repromptColumn = new DataColumn(nameof(c.Reprompt), typeof(short)); var repromptColumn = new DataColumn(nameof(c.Reprompt), typeof(short));
ciphersTable.Columns.Add(repromptColumn); ciphersTable.Columns.Add(repromptColumn);
var keyColummn = new DataColumn(nameof(c.Key), typeof(string));
ciphersTable.Columns.Add(keyColummn);
foreach (DataColumn col in ciphersTable.Columns) foreach (DataColumn col in ciphersTable.Columns)
{ {
@ -754,6 +758,7 @@ public class CipherRepository : Repository<Cipher, Guid>, ICipherRepository
row[revisionDateColumn] = cipher.RevisionDate; row[revisionDateColumn] = cipher.RevisionDate;
row[deletedDateColumn] = cipher.DeletedDate.HasValue ? (object)cipher.DeletedDate : DBNull.Value; row[deletedDateColumn] = cipher.DeletedDate.HasValue ? (object)cipher.DeletedDate : DBNull.Value;
row[repromptColumn] = cipher.Reprompt; row[repromptColumn] = cipher.Reprompt;
row[keyColummn] = cipher.Key;
ciphersTable.Rows.Add(row); ciphersTable.Rows.Add(row);
} }

View File

@ -73,6 +73,7 @@ public class UserCipherDetailsQuery : IQuery<CipherDetails>
Reprompt = c.Reprompt, Reprompt = c.Reprompt,
ViewPassword = true, ViewPassword = true,
OrganizationUseTotp = false, OrganizationUseTotp = false,
Key = c.Key
}); });
return union; return union;
} }

View File

@ -366,6 +366,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
Reprompt = c.Reprompt, Reprompt = c.Reprompt,
ViewPassword = true, ViewPassword = true,
OrganizationUseTotp = false, OrganizationUseTotp = false,
Key = c.Key
}; };
} }
var ciphers = await cipherDetailsView.ToListAsync(); var ciphers = await cipherDetailsView.ToListAsync();
@ -591,6 +592,7 @@ public class CipherRepository : Repository<Core.Vault.Entities.Cipher, Cipher, G
trackedCipher.Attachments = cipher.Attachments; trackedCipher.Attachments = cipher.Attachments;
trackedCipher.RevisionDate = cipher.RevisionDate; trackedCipher.RevisionDate = cipher.RevisionDate;
trackedCipher.DeletedDate = cipher.DeletedDate; trackedCipher.DeletedDate = cipher.DeletedDate;
trackedCipher.Key = cipher.Key;
await transaction.CommitAsync(); await transaction.CommitAsync();

View File

@ -29,6 +29,7 @@ public class CipherDetailsQuery : IQuery<CipherDetails>
RevisionDate = c.RevisionDate, RevisionDate = c.RevisionDate,
DeletedDate = c.DeletedDate, DeletedDate = c.DeletedDate,
Reprompt = c.Reprompt, Reprompt = c.Reprompt,
Key = c.Key,
Favorite = _userId.HasValue && c.Favorites != null && c.Favorites.ToLowerInvariant().Contains($"\"{_userId}\":true"), Favorite = _userId.HasValue && c.Favorites != null && c.Favorites.ToLowerInvariant().Contains($"\"{_userId}\":true"),
FolderId = (_ignoreFolders || !_userId.HasValue || c.Folders == null || !c.Folders.ToLowerInvariant().Contains(_userId.Value.ToString())) ? FolderId = (_ignoreFolders || !_userId.HasValue || c.Folders == null || !c.Folders.ToLowerInvariant().Contains(_userId.Value.ToString())) ?
null : null :

View File

@ -26,6 +26,7 @@ SELECT
ELSE TRY_CONVERT(UNIQUEIDENTIFIER, JSON_VALUE(C.[Folders], CONCAT('$."', @UserId, '"'))) ELSE TRY_CONVERT(UNIQUEIDENTIFIER, JSON_VALUE(C.[Folders], CONCAT('$."', @UserId, '"')))
END [FolderId], END [FolderId],
C.[DeletedDate], C.[DeletedDate],
C.[Reprompt] C.[Reprompt],
C.[Key]
FROM FROM
[dbo].[Cipher] C [dbo].[Cipher] C

View File

@ -15,7 +15,8 @@
@ViewPassword BIT, -- not used @ViewPassword BIT, -- not used
@OrganizationUseTotp BIT, -- not used @OrganizationUseTotp BIT, -- not used
@DeletedDate DATETIME2(7), @DeletedDate DATETIME2(7),
@Reprompt TINYINT @Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -35,7 +36,8 @@ BEGIN
[CreationDate], [CreationDate],
[RevisionDate], [RevisionDate],
[DeletedDate], [DeletedDate],
[Reprompt] [Reprompt],
[Key]
) )
VALUES VALUES
( (
@ -49,7 +51,8 @@ BEGIN
@CreationDate, @CreationDate,
@RevisionDate, @RevisionDate,
@DeletedDate, @DeletedDate,
@Reprompt @Reprompt,
@Key
) )
IF @OrganizationId IS NOT NULL IF @OrganizationId IS NOT NULL

View File

@ -16,6 +16,7 @@
@OrganizationUseTotp BIT, -- not used @OrganizationUseTotp BIT, -- not used
@DeletedDate DATETIME2(7), @DeletedDate DATETIME2(7),
@Reprompt TINYINT, @Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL,
@CollectionIds AS [dbo].[GuidIdArray] READONLY @CollectionIds AS [dbo].[GuidIdArray] READONLY
AS AS
BEGIN BEGIN
@ -23,7 +24,7 @@ BEGIN
EXEC [dbo].[CipherDetails_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders, EXEC [dbo].[CipherDetails_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders,
@Attachments, @CreationDate, @RevisionDate, @FolderId, @Favorite, @Edit, @ViewPassword, @Attachments, @CreationDate, @RevisionDate, @FolderId, @Favorite, @Edit, @ViewPassword,
@OrganizationUseTotp, @DeletedDate, @Reprompt @OrganizationUseTotp, @DeletedDate, @Reprompt, @Key
DECLARE @UpdateCollectionsSuccess INT DECLARE @UpdateCollectionsSuccess INT
EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds

View File

@ -6,7 +6,7 @@
@Data NVARCHAR(MAX), @Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX), -- not used @Favorites NVARCHAR(MAX), -- not used
@Folders NVARCHAR(MAX), -- not used @Folders NVARCHAR(MAX), -- not used
@Attachments NVARCHAR(MAX), -- not used @Attachments NVARCHAR(MAX),
@CreationDate DATETIME2(7), @CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7), @RevisionDate DATETIME2(7),
@FolderId UNIQUEIDENTIFIER, @FolderId UNIQUEIDENTIFIER,
@ -15,7 +15,8 @@
@ViewPassword BIT, -- not used @ViewPassword BIT, -- not used
@OrganizationUseTotp BIT, -- not used @OrganizationUseTotp BIT, -- not used
@DeletedDate DATETIME2(2), @DeletedDate DATETIME2(2),
@Reprompt TINYINT @Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -48,10 +49,12 @@ BEGIN
ELSE ELSE
JSON_MODIFY([Favorites], @UserIdPath, NULL) JSON_MODIFY([Favorites], @UserIdPath, NULL)
END, END,
[Attachments] = @Attachments,
[Reprompt] = @Reprompt, [Reprompt] = @Reprompt,
[CreationDate] = @CreationDate, [CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate, [RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate [DeletedDate] = @DeletedDate,
[Key] = @Key
WHERE WHERE
[Id] = @Id [Id] = @Id

View File

@ -10,7 +10,8 @@
@CreationDate DATETIME2(7), @CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7), @RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7), @DeletedDate DATETIME2(7),
@Reprompt TINYINT @Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -28,7 +29,8 @@ BEGIN
[CreationDate], [CreationDate],
[RevisionDate], [RevisionDate],
[DeletedDate], [DeletedDate],
[Reprompt] [Reprompt],
[Key]
) )
VALUES VALUES
( (
@ -43,7 +45,8 @@ BEGIN
@CreationDate, @CreationDate,
@RevisionDate, @RevisionDate,
@DeletedDate, @DeletedDate,
@Reprompt @Reprompt,
@Key
) )
IF @OrganizationId IS NOT NULL IF @OrganizationId IS NOT NULL

View File

@ -11,13 +11,14 @@
@RevisionDate DATETIME2(7), @RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7), @DeletedDate DATETIME2(7),
@Reprompt TINYINT, @Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL,
@CollectionIds AS [dbo].[GuidIdArray] READONLY @CollectionIds AS [dbo].[GuidIdArray] READONLY
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
EXEC [dbo].[Cipher_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders, EXEC [dbo].[Cipher_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders,
@Attachments, @CreationDate, @RevisionDate, @DeletedDate, @Reprompt @Attachments, @CreationDate, @RevisionDate, @DeletedDate, @Reprompt, @Key
DECLARE @UpdateCollectionsSuccess INT DECLARE @UpdateCollectionsSuccess INT
EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds

View File

@ -10,7 +10,8 @@
@CreationDate DATETIME2(7), @CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7), @RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7), @DeletedDate DATETIME2(7),
@Reprompt TINYINT @Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -28,7 +29,8 @@ BEGIN
[CreationDate] = @CreationDate, [CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate, [RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate, [DeletedDate] = @DeletedDate,
[Reprompt] = @Reprompt [Reprompt] = @Reprompt,
[Key] = @Key
WHERE WHERE
[Id] = @Id [Id] = @Id

View File

@ -11,6 +11,7 @@
@RevisionDate DATETIME2(7), @RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7), @DeletedDate DATETIME2(7),
@Reprompt TINYINT, @Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL,
@CollectionIds AS [dbo].[GuidIdArray] READONLY @CollectionIds AS [dbo].[GuidIdArray] READONLY
AS AS
BEGIN BEGIN
@ -36,7 +37,8 @@ BEGIN
[Data] = @Data, [Data] = @Data,
[Attachments] = @Attachments, [Attachments] = @Attachments,
[RevisionDate] = @RevisionDate, [RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate [DeletedDate] = @DeletedDate,
[Key] = @Key
-- No need to update CreationDate, Favorites, Folders, or Type since that data will not change -- No need to update CreationDate, Favorites, Folders, or Type since that data will not change
WHERE WHERE
[Id] = @Id [Id] = @Id

View File

@ -12,6 +12,7 @@ CREATE TABLE [dbo].[Cipher] (
[RevisionDate] DATETIME2 (7) NOT NULL, [RevisionDate] DATETIME2 (7) NOT NULL,
[DeletedDate] DATETIME2 (7) NULL, [DeletedDate] DATETIME2 (7) NULL,
[Reprompt] TINYINT NULL, [Reprompt] TINYINT NULL,
[Key] VARCHAR(MAX) NULL,
CONSTRAINT [PK_Cipher] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [PK_Cipher] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Cipher_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]), CONSTRAINT [FK_Cipher_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]),
CONSTRAINT [FK_Cipher_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) CONSTRAINT [FK_Cipher_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id])

View File

@ -2,6 +2,7 @@
using System.Reflection; using System.Reflection;
using Bit.Core; using Bit.Core;
using DbUp; using DbUp;
using DbUp.Helpers;
using Microsoft.Data.SqlClient; using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -24,6 +25,8 @@ public class DbMigrator
} }
public bool MigrateMsSqlDatabaseWithRetries(bool enableLogging = true, public bool MigrateMsSqlDatabaseWithRetries(bool enableLogging = true,
bool repeatable = false,
string folderName = MigratorConstants.DefaultMigrationsFolderName,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
var attempt = 1; var attempt = 1;
@ -32,7 +35,7 @@ public class DbMigrator
{ {
try try
{ {
var success = MigrateDatabase(enableLogging, cancellationToken); var success = MigrateDatabase(enableLogging, repeatable, folderName, cancellationToken);
return success; return success;
} }
catch (SqlException ex) catch (SqlException ex)
@ -54,6 +57,8 @@ public class DbMigrator
} }
public bool MigrateDatabase(bool enableLogging = true, public bool MigrateDatabase(bool enableLogging = true,
bool repeatable = false,
string folderName = MigratorConstants.DefaultMigrationsFolderName,
CancellationToken cancellationToken = default(CancellationToken)) CancellationToken cancellationToken = default(CancellationToken))
{ {
if (_logger != null) if (_logger != null)
@ -98,12 +103,20 @@ public class DbMigrator
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
var builder = DeployChanges.To var builder = DeployChanges.To
.SqlDatabase(_connectionString) .SqlDatabase(_connectionString)
.JournalToSqlTable("dbo", "Migration")
.WithScriptsAndCodeEmbeddedInAssembly(Assembly.GetExecutingAssembly(), .WithScriptsAndCodeEmbeddedInAssembly(Assembly.GetExecutingAssembly(),
s => s.Contains($".DbScripts.") && !s.Contains(".Archive.")) s => s.Contains($".{folderName}.") && !s.Contains(".Archive."))
.WithTransaction() .WithTransaction()
.WithExecutionTimeout(new TimeSpan(0, 5, 0)); .WithExecutionTimeout(new TimeSpan(0, 5, 0));
if (repeatable)
{
builder.JournalTo(new NullJournal());
}
else
{
builder.JournalToSqlTable("dbo", MigratorConstants.SqlTableJournalName);
}
if (enableLogging) if (enableLogging)
{ {
if (_logger != null) if (_logger != null)

View File

@ -0,0 +1,428 @@
-- Add Key Column
IF COL_LENGTH('[dbo].[Cipher]', 'Key') IS NULL
BEGIN
ALTER TABLE [dbo].[Cipher] ADD [Key] VARCHAR (MAX) NULL;
END
GO
-- Remove ForceKeyRotation Column from the Cipher table
-- (this is to help with the dev work as the column was no longer needed)
IF COL_LENGTH('[dbo].[Cipher]', 'ForceKeyRotation') IS NOT NULL
BEGIN
ALTER TABLE [dbo].[Cipher] DROP CONSTRAINT [D_Cipher_ForceKeyRotation];
ALTER TABLE [dbo].[Cipher] DROP COLUMN [ForceKeyRotation];
END
GO
CREATE OR ALTER FUNCTION [dbo].[CipherDetails](@UserId UNIQUEIDENTIFIER)
RETURNS TABLE
AS RETURN
SELECT
C.[Id],
C.[UserId],
C.[OrganizationId],
C.[Type],
C.[Data],
C.[Attachments],
C.[CreationDate],
C.[RevisionDate],
CASE
WHEN
@UserId IS NULL
OR C.[Favorites] IS NULL
OR JSON_VALUE(C.[Favorites], CONCAT('$."', @UserId, '"')) IS NULL
THEN 0
ELSE 1
END [Favorite],
CASE
WHEN
@UserId IS NULL
OR C.[Folders] IS NULL
THEN NULL
ELSE TRY_CONVERT(UNIQUEIDENTIFIER, JSON_VALUE(C.[Folders], CONCAT('$."', @UserId, '"')))
END [FolderId],
C.[DeletedDate],
C.[Reprompt],
C.[Key]
FROM
[dbo].[Cipher] C
GO
IF OBJECT_ID('[dbo].[UserCipherDetails]') IS NOT NULL
BEGIN
EXECUTE sp_refreshsqlmodule N'[dbo].[UserCipherDetails]';
END
GO
IF OBJECT_ID('[dbo].[CipherView]') IS NOT NULL
BEGIN
EXECUTE sp_refreshsqlmodule N'[dbo].[CipherView]';
END
GO
CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Create]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX), -- not used
@Folders NVARCHAR(MAX), -- not used
@Attachments NVARCHAR(MAX), -- not used
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@FolderId UNIQUEIDENTIFIER,
@Favorite BIT,
@Edit BIT, -- not used
@ViewPassword BIT, -- not used
@OrganizationUseTotp BIT, -- not used
@DeletedDate DATETIME2(7),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
DECLARE @UserIdKey VARCHAR(50) = CONCAT('"', @UserId, '"')
DECLARE @UserIdPath VARCHAR(50) = CONCAT('$.', @UserIdKey)
INSERT INTO [dbo].[Cipher]
(
[Id],
[UserId],
[OrganizationId],
[Type],
[Data],
[Favorites],
[Folders],
[CreationDate],
[RevisionDate],
[DeletedDate],
[Reprompt],
[Key]
)
VALUES
(
@Id,
CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END,
@OrganizationId,
@Type,
@Data,
CASE WHEN @Favorite = 1 THEN CONCAT('{', @UserIdKey, ':true}') ELSE NULL END,
CASE WHEN @FolderId IS NOT NULL THEN CONCAT('{', @UserIdKey, ':"', @FolderId, '"', '}') ELSE NULL END,
@CreationDate,
@RevisionDate,
@DeletedDate,
@Reprompt,
@Key
)
IF @OrganizationId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
END
ELSE IF @UserId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
END
END
GO
CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_Update]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX), -- not used
@Folders NVARCHAR(MAX), -- not used
@Attachments NVARCHAR(MAX),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@FolderId UNIQUEIDENTIFIER,
@Favorite BIT,
@Edit BIT, -- not used
@ViewPassword BIT, -- not used
@OrganizationUseTotp BIT, -- not used
@DeletedDate DATETIME2(2),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
DECLARE @UserIdKey VARCHAR(50) = CONCAT('"', @UserId, '"')
DECLARE @UserIdPath VARCHAR(50) = CONCAT('$.', @UserIdKey)
UPDATE
[dbo].[Cipher]
SET
[UserId] = CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END,
[OrganizationId] = @OrganizationId,
[Type] = @Type,
[Data] = @Data,
[Folders] =
CASE
WHEN @FolderId IS NOT NULL AND [Folders] IS NULL THEN
CONCAT('{', @UserIdKey, ':"', @FolderId, '"', '}')
WHEN @FolderId IS NOT NULL THEN
JSON_MODIFY([Folders], @UserIdPath, CAST(@FolderId AS VARCHAR(50)))
ELSE
JSON_MODIFY([Folders], @UserIdPath, NULL)
END,
[Favorites] =
CASE
WHEN @Favorite = 1 AND [Favorites] IS NULL THEN
CONCAT('{', @UserIdKey, ':true}')
WHEN @Favorite = 1 THEN
JSON_MODIFY([Favorites], @UserIdPath, CAST(1 AS BIT))
ELSE
JSON_MODIFY([Favorites], @UserIdPath, NULL)
END,
[Attachments] = @Attachments,
[Reprompt] = @Reprompt,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate,
[Key] = @Key
WHERE
[Id] = @Id
IF @OrganizationId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
END
ELSE IF @UserId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
END
END
GO
CREATE OR ALTER PROCEDURE [dbo].[Cipher_Update]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX),
@Folders NVARCHAR(MAX),
@Attachments NVARCHAR(MAX),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
UPDATE
[dbo].[Cipher]
SET
[UserId] = CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END,
[OrganizationId] = @OrganizationId,
[Type] = @Type,
[Data] = @Data,
[Favorites] = @Favorites,
[Folders] = @Folders,
[Attachments] = @Attachments,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate,
[Reprompt] = @Reprompt,
[Key] = @Key
WHERE
[Id] = @Id
IF @OrganizationId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
END
ELSE IF @UserId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
END
END
GO
CREATE OR ALTER PROCEDURE [dbo].[Cipher_Create]
@Id UNIQUEIDENTIFIER OUTPUT,
@UserId UNIQUEIDENTIFIER,
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX),
@Folders NVARCHAR(MAX),
@Attachments NVARCHAR(MAX),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[Cipher]
(
[Id],
[UserId],
[OrganizationId],
[Type],
[Data],
[Favorites],
[Folders],
[Attachments],
[CreationDate],
[RevisionDate],
[DeletedDate],
[Reprompt],
[Key]
)
VALUES
(
@Id,
CASE WHEN @OrganizationId IS NULL THEN @UserId ELSE NULL END,
@OrganizationId,
@Type,
@Data,
@Favorites,
@Folders,
@Attachments,
@CreationDate,
@RevisionDate,
@DeletedDate,
@Reprompt,
@Key
)
IF @OrganizationId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
END
ELSE IF @UserId IS NOT NULL
BEGIN
EXEC [dbo].[User_BumpAccountRevisionDate] @UserId
END
END
GO
CREATE OR ALTER PROCEDURE [dbo].[Cipher_CreateWithCollections]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX),
@Folders NVARCHAR(MAX),
@Attachments NVARCHAR(MAX),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL,
@CollectionIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
EXEC [dbo].[Cipher_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders,
@Attachments, @CreationDate, @RevisionDate, @DeletedDate, @Reprompt, @Key
DECLARE @UpdateCollectionsSuccess INT
EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds
END
GO
CREATE OR ALTER PROCEDURE [dbo].[Cipher_UpdateWithCollections]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX),
@Folders NVARCHAR(MAX),
@Attachments NVARCHAR(MAX),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@DeletedDate DATETIME2(7),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL,
@CollectionIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
BEGIN TRANSACTION Cipher_UpdateWithCollections
DECLARE @UpdateCollectionsSuccess INT
EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds
IF @UpdateCollectionsSuccess < 0
BEGIN
COMMIT TRANSACTION Cipher_UpdateWithCollections
SELECT -1 -- -1 = Failure
RETURN
END
UPDATE
[dbo].[Cipher]
SET
[UserId] = NULL,
[OrganizationId] = @OrganizationId,
[Data] = @Data,
[Attachments] = @Attachments,
[RevisionDate] = @RevisionDate,
[DeletedDate] = @DeletedDate,
[Key] = @Key
-- No need to update CreationDate, Favorites, Folders, or Type since that data will not change
WHERE
[Id] = @Id
COMMIT TRANSACTION Cipher_UpdateWithCollections
IF @Attachments IS NOT NULL
BEGIN
EXEC [dbo].[Organization_UpdateStorage] @OrganizationId
EXEC [dbo].[User_UpdateStorage] @UserId
END
EXEC [dbo].[User_BumpAccountRevisionDateByCipherId] @Id, @OrganizationId
SELECT 0 -- 0 = Success
END
GO
CREATE OR ALTER PROCEDURE [dbo].[CipherDetails_CreateWithCollections]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@OrganizationId UNIQUEIDENTIFIER,
@Type TINYINT,
@Data NVARCHAR(MAX),
@Favorites NVARCHAR(MAX), -- not used
@Folders NVARCHAR(MAX), -- not used
@Attachments NVARCHAR(MAX), -- not used
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@FolderId UNIQUEIDENTIFIER,
@Favorite BIT,
@Edit BIT, -- not used
@ViewPassword BIT, -- not used
@OrganizationUseTotp BIT, -- not used
@DeletedDate DATETIME2(7),
@Reprompt TINYINT,
@Key VARCHAR(MAX) = NULL,
@CollectionIds AS [dbo].[GuidIdArray] READONLY
AS
BEGIN
SET NOCOUNT ON
EXEC [dbo].[CipherDetails_Create] @Id, @UserId, @OrganizationId, @Type, @Data, @Favorites, @Folders,
@Attachments, @CreationDate, @RevisionDate, @FolderId, @Favorite, @Edit, @ViewPassword,
@OrganizationUseTotp, @DeletedDate, @Reprompt, @Key
DECLARE @UpdateCollectionsSuccess INT
EXEC @UpdateCollectionsSuccess = [dbo].[Cipher_UpdateCollections] @Id, @UserId, @OrganizationId, @CollectionIds
END
GO

View File

@ -2,6 +2,7 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="DbScripts\**\*.sql" /> <EmbeddedResource Include="DbScripts\**\*.sql" />
<EmbeddedResource Include="DbScripts_data_migration\**\*.sql" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -0,0 +1,8 @@
namespace Bit.Migrator;
public static class MigratorConstants
{
public const string SqlTableJournalName = "Migration";
public const string DefaultMigrationsFolderName = "DbScripts";
public const string TransitionMigrationsFolderName = "DbScripts_data_migration";
}

View File

@ -70,7 +70,7 @@ public class SqlServerDbMigrator : IDbMigrator
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
var builder = DeployChanges.To var builder = DeployChanges.To
.SqlDatabase(_connectionString) .SqlDatabase(_connectionString)
.JournalToSqlTable("dbo", "Migration") .JournalToSqlTable("dbo", MigratorConstants.SqlTableJournalName)
.WithScriptsAndCodeEmbeddedInAssembly(Assembly.GetExecutingAssembly(), .WithScriptsAndCodeEmbeddedInAssembly(Assembly.GetExecutingAssembly(),
s => s.Contains($".DbScripts.") && !s.Contains(".Archive.")) s => s.Contains($".DbScripts.") && !s.Contains(".Archive."))
.WithTransaction() .WithTransaction()

View File

@ -10,6 +10,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandDotNet" Version="7.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -1,55 +1,51 @@
using Bit.Migrator; using Bit.Migrator;
using CommandDotNet;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
internal class Program internal class Program
{ {
private static IDictionary<string, string> Parameters { get; set; }
private static int Main(string[] args) private static int Main(string[] args)
{ {
if (args.Length == 0) return new AppRunner<Program>().Run(args);
{
Console.WriteLine("Please enter a database connection string argument.");
WriteUsageToConsole();
return 1;
}
if (args.Length == 1 && (args[0] == "--verbose" || args[0] == "-v"))
{
Console.WriteLine($"Please enter a database connection string argument before {args[0]} option.");
WriteUsageToConsole();
return 1;
}
var databaseConnectionString = args[0];
var verbose = false;
if (args.Length == 2 && (args[1] == "--verbose" || args[1] == "-v"))
{
verbose = true;
}
var success = MigrateDatabase(databaseConnectionString, verbose);
if (!success)
{
return -1;
}
return 0;
} }
[DefaultCommand]
public void Execute(
[Operand(Description = "Database connection string")]
string databaseConnectionString,
[Option('v', "verbose", Description = "Enable verbose output of migrator logs")]
bool verbose = false,
[Option('r', "repeatable", Description = "Mark scripts as repeatable")]
bool repeatable = false,
[Option('f', "folder", Description = "Folder name of database scripts")]
string folderName = MigratorConstants.DefaultMigrationsFolderName) => MigrateDatabase(databaseConnectionString, verbose, repeatable, folderName);
private static void WriteUsageToConsole() private static void WriteUsageToConsole()
{ {
Console.WriteLine("Usage: MsSqlMigratorUtility <database-connection-string>"); Console.WriteLine("Usage: MsSqlMigratorUtility <database-connection-string>");
Console.WriteLine("Usage: MsSqlMigratorUtility <database-connection-string> -v|--verbose (for verbose output of migrator logs)"); Console.WriteLine("Usage: MsSqlMigratorUtility <database-connection-string> -v|--verbose (for verbose output of migrator logs)");
Console.WriteLine("Usage: MsSqlMigratorUtility <database-connection-string> -r|--repeatable (for marking scripts as repeatable) -f|--folder <folder-name-in-migrator-project> (for specifying folder name of scripts)");
Console.WriteLine("Usage: MsSqlMigratorUtility <database-connection-string> -v|--verbose (for verbose output of migrator logs) -r|--repeatable (for marking scripts as repeatable) -f|--folder <folder-name-in-migrator-project> (for specifying folder name of scripts)");
} }
private static bool MigrateDatabase(string databaseConnectionString, bool verbose = false, int attempt = 1) private static bool MigrateDatabase(string databaseConnectionString, bool verbose = false, bool repeatable = false, string folderName = "")
{ {
var logger = CreateLogger(verbose); var logger = CreateLogger(verbose);
logger.LogInformation($"Migrating database with repeatable: {repeatable} and folderName: {folderName}.");
var migrator = new DbMigrator(databaseConnectionString, logger); var migrator = new DbMigrator(databaseConnectionString, logger);
var success = migrator.MigrateMsSqlDatabaseWithRetries(verbose); bool success = false;
if (!string.IsNullOrWhiteSpace(folderName))
{
success = migrator.MigrateMsSqlDatabaseWithRetries(verbose, repeatable, folderName);
}
else
{
success = migrator.MigrateMsSqlDatabaseWithRetries(verbose, repeatable);
}
return success; return success;
} }

View File

@ -2,6 +2,15 @@
"version": 1, "version": 1,
"dependencies": { "dependencies": {
"net6.0": { "net6.0": {
"CommandDotNet": {
"type": "Direct",
"requested": "[7.0.2, )",
"resolved": "7.0.2",
"contentHash": "sFfqn4T6Ux4AbGnhS+BZvf9iVeP6b9p9bwaMT8nsefVcYH2tC9MHj3AoY+GG0nnBPmFawRqdgud08cBjBdPByQ==",
"dependencies": {
"Microsoft.CSharp": "4.7.0"
}
},
"Microsoft.Extensions.Logging": { "Microsoft.Extensions.Logging": {
"type": "Direct", "type": "Direct",
"requested": "[6.0.0, )", "requested": "[6.0.0, )",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.MySqlMigrations.Migrations;
public partial class CipherKeyUpdate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Key",
table: "Cipher",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Key",
table: "Cipher");
}
}

View File

@ -1498,6 +1498,9 @@ namespace Bit.MySqlMigrations.Migrations
b.Property<string>("Folders") b.Property<string>("Folders")
.HasColumnType("longtext"); .HasColumnType("longtext");
b.Property<string>("Key")
.HasColumnType("longtext");
b.Property<Guid?>("OrganizationId") b.Property<Guid?>("OrganizationId")
.HasColumnType("char(36)"); .HasColumnType("char(36)");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.PostgresMigrations.Migrations;
public partial class CipherKeyUpdate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Key",
table: "Cipher",
type: "text",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Key",
table: "Cipher");
}
}

View File

@ -1509,6 +1509,9 @@ namespace Bit.PostgresMigrations.Migrations
b.Property<string>("Folders") b.Property<string>("Folders")
.HasColumnType("text"); .HasColumnType("text");
b.Property<string>("Key")
.HasColumnType("text");
b.Property<Guid?>("OrganizationId") b.Property<Guid?>("OrganizationId")
.HasColumnType("uuid"); .HasColumnType("uuid");

View File

@ -190,7 +190,12 @@ public class Program
var vaultConnectionString = Helpers.GetValueFromEnvFile("global", var vaultConnectionString = Helpers.GetValueFromEnvFile("global",
"globalSettings__sqlServer__connectionString"); "globalSettings__sqlServer__connectionString");
var migrator = new DbMigrator(vaultConnectionString, null); var migrator = new DbMigrator(vaultConnectionString, null);
migrator.MigrateMsSqlDatabaseWithRetries(false);
var log = false;
migrator.MigrateMsSqlDatabaseWithRetries(log);
migrator.MigrateMsSqlDatabaseWithRetries(log, true, MigratorConstants.TransitionMigrationsFolderName);
} }
private static bool ValidateInstallation() private static bool ValidateInstallation()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.SqliteMigrations.Migrations;
public partial class CipherKeyUpdate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Key",
table: "Cipher",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Key",
table: "Cipher");
}
}

View File

@ -1496,6 +1496,9 @@ namespace Bit.SqliteMigrations.Migrations
b.Property<string>("Folders") b.Property<string>("Folders")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<string>("Key")
.HasColumnType("TEXT");
b.Property<Guid?>("OrganizationId") b.Property<Guid?>("OrganizationId")
.HasColumnType("TEXT"); .HasColumnType("TEXT");