1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-23 22:15:10 -05:00

[PM-20064] Add cascade deletion for cipher with tasks (#5690)

* add cascade deletion for cipher tasks

* add migrations for cascade delete on ciphers and security tasks

* remove trailing comma

* add SQL migration for PasswordHealthReportApplication

- Allow cascade delete when an organization is deleted
This commit is contained in:
Nick Krantz 2025-04-23 13:16:29 -05:00 committed by GitHub
parent 90d831d9ef
commit 6809709628
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 19009 additions and 8 deletions

View File

@ -34,6 +34,12 @@ public class NotificationEntityTypeConfiguration : IEntityTypeConfiguration<Noti
.HasIndex(n => n.TaskId)
.IsClustered(false);
builder
.HasOne(n => n.Task)
.WithMany()
.HasForeignKey(n => n.TaskId)
.OnDelete(DeleteBehavior.Cascade);
builder.ToTable(nameof(Notification));
}
}

View File

@ -24,6 +24,18 @@ public class SecurityTaskEntityTypeConfiguration : IEntityTypeConfiguration<Secu
.HasIndex(s => s.CipherId)
.IsClustered(false);
builder
.HasOne(p => p.Organization)
.WithMany()
.HasForeignKey(p => p.OrganizationId)
.OnDelete(DeleteBehavior.Cascade);
builder
.HasOne(p => p.Cipher)
.WithMany()
.HasForeignKey(p => p.CipherId)
.OnDelete(DeleteBehavior.Cascade);
builder
.ToTable(nameof(SecurityTask));
}

View File

@ -14,7 +14,7 @@ CREATE TABLE [dbo].[Notification]
CONSTRAINT [PK_Notification] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Notification_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]),
CONSTRAINT [FK_Notification_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]),
CONSTRAINT [FK_Notification_SecurityTask] FOREIGN KEY ([TaskId]) REFERENCES [dbo].[SecurityTask] ([Id])
CONSTRAINT [FK_Notification_SecurityTask] FOREIGN KEY ([TaskId]) REFERENCES [dbo].[SecurityTask] ([Id]) ON DELETE CASCADE
);

View File

@ -6,7 +6,7 @@ CREATE TABLE [dbo].[PasswordHealthReportApplication]
CreationDate DATETIME2(7) NOT NULL,
RevisionDate DATETIME2(7) NOT NULL,
CONSTRAINT [PK_PasswordHealthReportApplication] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_PasswordHealthReportApplication_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]),
CONSTRAINT [FK_PasswordHealthReportApplication_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE
);
GO

View File

@ -1,6 +1,7 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Models.Data;
@ -912,4 +913,59 @@ public class CipherRepositoryTests
Assert.Equal(CipherType.SecureNote, updatedCipher1.Type);
Assert.Equal("new_attachments", updatedCipher2.Attachments);
}
[DatabaseTheory, DatabaseData]
public async Task DeleteCipherWithSecurityTaskAsync_Works(
IOrganizationRepository organizationRepository,
ICipherRepository cipherRepository,
ISecurityTaskRepository securityTaskRepository)
{
var organization = await organizationRepository.CreateAsync(new Organization
{
Name = "Test Org",
PlanType = PlanType.EnterpriseAnnually,
Plan = "Test Plan",
BillingEmail = ""
});
var cipher1 = new Cipher { Type = CipherType.Login, OrganizationId = organization.Id, Data = "", };
await cipherRepository.CreateAsync(cipher1);
var cipher2 = new Cipher { Type = CipherType.Login, OrganizationId = organization.Id, Data = "", };
await cipherRepository.CreateAsync(cipher2);
var tasks = new List<SecurityTask>
{
new()
{
OrganizationId = organization.Id,
CipherId = cipher1.Id,
Status = SecurityTaskStatus.Pending,
Type = SecurityTaskType.UpdateAtRiskCredential,
},
new()
{
OrganizationId = organization.Id,
CipherId = cipher2.Id,
Status = SecurityTaskStatus.Completed,
Type = SecurityTaskType.UpdateAtRiskCredential,
}
};
await securityTaskRepository.CreateManyAsync(tasks);
// Delete cipher with pending security task
await cipherRepository.DeleteAsync(cipher1);
var deletedCipher1 = await cipherRepository.GetByIdAsync(cipher1.Id);
Assert.Null(deletedCipher1);
// Delete cipher with completed security task
await cipherRepository.DeleteAsync(cipher2);
var deletedCipher2 = await cipherRepository.GetByIdAsync(cipher2.Id);
Assert.Null(deletedCipher2);
}
}

View File

@ -0,0 +1,11 @@
BEGIN
IF EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_Notification_SecurityTask')
BEGIN
ALTER TABLE [dbo].[Notification]
DROP CONSTRAINT [FK_Notification_SecurityTask]
END
ALTER TABLE [dbo].[Notification]
ADD CONSTRAINT [FK_Notification_SecurityTask] FOREIGN KEY ([TaskId]) REFERENCES [dbo].[SecurityTask] ([Id]) ON DELETE CASCADE
END
GO

View File

@ -0,0 +1,11 @@
BEGIN
IF EXISTS (SELECT 1 FROM sys.foreign_keys WHERE name = 'FK_PasswordHealthReportApplication_Organization')
BEGIN
ALTER TABLE [dbo].[PasswordHealthReportApplication]
DROP CONSTRAINT [FK_PasswordHealthReportApplication_Organization]
END
ALTER TABLE [dbo].[PasswordHealthReportApplication]
ADD CONSTRAINT [FK_PasswordHealthReportApplication_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]) ON DELETE CASCADE
END
GO

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.MySqlMigrations.Migrations;
/// <inheritdoc />
public partial class NotificationCascadeDelete : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Notification_SecurityTask_TaskId",
table: "Notification");
migrationBuilder.AddForeignKey(
name: "FK_Notification_SecurityTask_TaskId",
table: "Notification",
column: "TaskId",
principalTable: "SecurityTask",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Notification_SecurityTask_TaskId",
table: "Notification");
migrationBuilder.AddForeignKey(
name: "FK_Notification_SecurityTask_TaskId",
table: "Notification",
column: "TaskId",
principalTable: "SecurityTask",
principalColumn: "Id");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.MySqlMigrations.Migrations;
/// <inheritdoc />
public partial class SecurityTaskCascadeDelete : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_SecurityTask_Cipher_CipherId",
table: "SecurityTask");
migrationBuilder.AddForeignKey(
name: "FK_SecurityTask_Cipher_CipherId",
table: "SecurityTask",
column: "CipherId",
principalTable: "Cipher",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_SecurityTask_Cipher_CipherId",
table: "SecurityTask");
migrationBuilder.AddForeignKey(
name: "FK_SecurityTask_Cipher_CipherId",
table: "SecurityTask",
column: "CipherId",
principalTable: "Cipher",
principalColumn: "Id");
}
}

View File

@ -2737,7 +2737,8 @@ namespace Bit.MySqlMigrations.Migrations
b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task")
.WithMany()
.HasForeignKey("TaskId");
.HasForeignKey("TaskId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User")
.WithMany()
@ -2852,7 +2853,8 @@ namespace Bit.MySqlMigrations.Migrations
{
b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher")
.WithMany()
.HasForeignKey("CipherId");
.HasForeignKey("CipherId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
.WithMany()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.PostgresMigrations.Migrations;
/// <inheritdoc />
public partial class NotificationCascadeDelete : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Notification_SecurityTask_TaskId",
table: "Notification");
migrationBuilder.AddForeignKey(
name: "FK_Notification_SecurityTask_TaskId",
table: "Notification",
column: "TaskId",
principalTable: "SecurityTask",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Notification_SecurityTask_TaskId",
table: "Notification");
migrationBuilder.AddForeignKey(
name: "FK_Notification_SecurityTask_TaskId",
table: "Notification",
column: "TaskId",
principalTable: "SecurityTask",
principalColumn: "Id");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.PostgresMigrations.Migrations;
/// <inheritdoc />
public partial class SecurityTaskCascadeDelete : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_SecurityTask_Cipher_CipherId",
table: "SecurityTask");
migrationBuilder.AddForeignKey(
name: "FK_SecurityTask_Cipher_CipherId",
table: "SecurityTask",
column: "CipherId",
principalTable: "Cipher",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_SecurityTask_Cipher_CipherId",
table: "SecurityTask");
migrationBuilder.AddForeignKey(
name: "FK_SecurityTask_Cipher_CipherId",
table: "SecurityTask",
column: "CipherId",
principalTable: "Cipher",
principalColumn: "Id");
}
}

View File

@ -2743,7 +2743,8 @@ namespace Bit.PostgresMigrations.Migrations
b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task")
.WithMany()
.HasForeignKey("TaskId");
.HasForeignKey("TaskId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User")
.WithMany()
@ -2858,7 +2859,8 @@ namespace Bit.PostgresMigrations.Migrations
{
b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher")
.WithMany()
.HasForeignKey("CipherId");
.HasForeignKey("CipherId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
.WithMany()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.SqliteMigrations.Migrations;
/// <inheritdoc />
public partial class NotificationCascadeDelete : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Notification_SecurityTask_TaskId",
table: "Notification");
migrationBuilder.AddForeignKey(
name: "FK_Notification_SecurityTask_TaskId",
table: "Notification",
column: "TaskId",
principalTable: "SecurityTask",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Notification_SecurityTask_TaskId",
table: "Notification");
migrationBuilder.AddForeignKey(
name: "FK_Notification_SecurityTask_TaskId",
table: "Notification",
column: "TaskId",
principalTable: "SecurityTask",
principalColumn: "Id");
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.SqliteMigrations.Migrations;
/// <inheritdoc />
public partial class SecurityTaskCascadeDelete : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_SecurityTask_Cipher_CipherId",
table: "SecurityTask");
migrationBuilder.AddForeignKey(
name: "FK_SecurityTask_Cipher_CipherId",
table: "SecurityTask",
column: "CipherId",
principalTable: "Cipher",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_SecurityTask_Cipher_CipherId",
table: "SecurityTask");
migrationBuilder.AddForeignKey(
name: "FK_SecurityTask_Cipher_CipherId",
table: "SecurityTask",
column: "CipherId",
principalTable: "Cipher",
principalColumn: "Id");
}
}

View File

@ -2726,7 +2726,8 @@ namespace Bit.SqliteMigrations.Migrations
b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.SecurityTask", "Task")
.WithMany()
.HasForeignKey("TaskId");
.HasForeignKey("TaskId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Bit.Infrastructure.EntityFramework.Models.User", "User")
.WithMany()
@ -2841,7 +2842,8 @@ namespace Bit.SqliteMigrations.Migrations
{
b.HasOne("Bit.Infrastructure.EntityFramework.Vault.Models.Cipher", "Cipher")
.WithMany()
.HasForeignKey("CipherId");
.HasForeignKey("CipherId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Bit.Infrastructure.EntityFramework.AdminConsole.Models.Organization", "Organization")
.WithMany()