1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-28 13:15:12 +01:00

Device deactivation (#4963)

* Device deactivation

* Check active status in service

* Format and work around potential deadlocks
This commit is contained in:
Matt Bishop 2024-10-31 17:05:13 -04:00 committed by GitHub
parent 751fd33aef
commit a04df4beba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 8801 additions and 36 deletions

View File

@ -196,8 +196,8 @@ public class DevicesController : Controller
} }
[HttpDelete("{id}")] [HttpDelete("{id}")]
[HttpPost("{id}/delete")] [HttpPost("{id}/deactivate")]
public async Task Delete(string id) public async Task Deactivate(string id)
{ {
var device = await _deviceRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value); var device = await _deviceRepository.GetByIdAsync(new Guid(id), _userService.GetProperUserId(User).Value);
if (device == null) if (device == null)
@ -205,7 +205,7 @@ public class DevicesController : Controller
throw new NotFoundException(); throw new NotFoundException();
} }
await _deviceService.DeleteAsync(device); await _deviceService.DeactivateAsync(device);
} }
[AllowAnonymous] [AllowAnonymous]

View File

@ -38,6 +38,10 @@ public class Device : ITableObject<Guid>
/// </summary> /// </summary>
public string? EncryptedPrivateKey { get; set; } public string? EncryptedPrivateKey { get; set; }
/// <summary>
/// Whether the device is active for the user.
/// </summary>
public bool Active { get; set; } = true;
public void SetNewId() public void SetNewId()
{ {

View File

@ -7,7 +7,7 @@ public interface IDeviceService
{ {
Task SaveAsync(Device device); Task SaveAsync(Device device);
Task ClearTokenAsync(Device device); Task ClearTokenAsync(Device device);
Task DeleteAsync(Device device); Task DeactivateAsync(Device device);
Task UpdateDevicesTrustAsync(string currentDeviceIdentifier, Task UpdateDevicesTrustAsync(string currentDeviceIdentifier,
Guid currentUserId, Guid currentUserId,
DeviceKeysUpdateRequestModel currentDeviceUpdate, DeviceKeysUpdateRequestModel currentDeviceUpdate,

View File

@ -41,9 +41,18 @@ public class DeviceService : IDeviceService
await _pushRegistrationService.DeleteRegistrationAsync(device.Id.ToString()); await _pushRegistrationService.DeleteRegistrationAsync(device.Id.ToString());
} }
public async Task DeleteAsync(Device device) public async Task DeactivateAsync(Device device)
{ {
await _deviceRepository.DeleteAsync(device); // already deactivated
if (!device.Active)
{
return;
}
device.Active = false;
device.RevisionDate = DateTime.UtcNow;
await _deviceRepository.UpsertAsync(device);
await _pushRegistrationService.DeleteRegistrationAsync(device.Id.ToString()); await _pushRegistrationService.DeleteRegistrationAsync(device.Id.ToString());
} }

View File

@ -21,6 +21,10 @@ public class DeviceEntityTypeConfiguration : IEntityTypeConfiguration<Device>
.HasIndex(d => d.Identifier) .HasIndex(d => d.Identifier)
.IsClustered(false); .IsClustered(false);
builder.Property(c => c.Active)
.ValueGeneratedNever()
.HasDefaultValue(true);
builder.ToTable(nameof(Device)); builder.ToTable(nameof(Device));
} }
} }

View File

@ -9,7 +9,8 @@
@RevisionDate DATETIME2(7), @RevisionDate DATETIME2(7),
@EncryptedUserKey VARCHAR(MAX) = NULL, @EncryptedUserKey VARCHAR(MAX) = NULL,
@EncryptedPublicKey VARCHAR(MAX) = NULL, @EncryptedPublicKey VARCHAR(MAX) = NULL,
@EncryptedPrivateKey VARCHAR(MAX) = NULL @EncryptedPrivateKey VARCHAR(MAX) = NULL,
@Active BIT = 1
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -26,7 +27,8 @@ BEGIN
[RevisionDate], [RevisionDate],
[EncryptedUserKey], [EncryptedUserKey],
[EncryptedPublicKey], [EncryptedPublicKey],
[EncryptedPrivateKey] [EncryptedPrivateKey],
[Active]
) )
VALUES VALUES
( (
@ -40,6 +42,7 @@ BEGIN
@RevisionDate, @RevisionDate,
@EncryptedUserKey, @EncryptedUserKey,
@EncryptedPublicKey, @EncryptedPublicKey,
@EncryptedPrivateKey @EncryptedPrivateKey,
@Active
) )
END END

View File

@ -1,12 +0,0 @@
CREATE PROCEDURE [dbo].[Device_DeleteById]
@Id UNIQUEIDENTIFIER
AS
BEGIN
SET NOCOUNT ON
DELETE
FROM
[dbo].[Device]
WHERE
[Id] = @Id
END

View File

@ -9,7 +9,8 @@
@RevisionDate DATETIME2(7), @RevisionDate DATETIME2(7),
@EncryptedUserKey VARCHAR(MAX) = NULL, @EncryptedUserKey VARCHAR(MAX) = NULL,
@EncryptedPublicKey VARCHAR(MAX) = NULL, @EncryptedPublicKey VARCHAR(MAX) = NULL,
@EncryptedPrivateKey VARCHAR(MAX) = NULL @EncryptedPrivateKey VARCHAR(MAX) = NULL,
@Active BIT = 1
AS AS
BEGIN BEGIN
SET NOCOUNT ON SET NOCOUNT ON
@ -26,7 +27,8 @@ BEGIN
[RevisionDate] = @RevisionDate, [RevisionDate] = @RevisionDate,
[EncryptedUserKey] = @EncryptedUserKey, [EncryptedUserKey] = @EncryptedUserKey,
[EncryptedPublicKey] = @EncryptedPublicKey, [EncryptedPublicKey] = @EncryptedPublicKey,
[EncryptedPrivateKey] = @EncryptedPrivateKey [EncryptedPrivateKey] = @EncryptedPrivateKey,
[Active] = @Active
WHERE WHERE
[Id] = @Id [Id] = @Id
END END

View File

@ -10,16 +10,15 @@
[EncryptedUserKey] VARCHAR (MAX) NULL, [EncryptedUserKey] VARCHAR (MAX) NULL,
[EncryptedPublicKey] VARCHAR (MAX) NULL, [EncryptedPublicKey] VARCHAR (MAX) NULL,
[EncryptedPrivateKey] VARCHAR (MAX) NULL, [EncryptedPrivateKey] VARCHAR (MAX) NULL,
[Active] BIT NOT NULL CONSTRAINT [DF_Device_Active] DEFAULT (1),
CONSTRAINT [PK_Device] PRIMARY KEY CLUSTERED ([Id] ASC), CONSTRAINT [PK_Device] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Device_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]) CONSTRAINT [FK_Device_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id])
); );
GO GO
CREATE UNIQUE NONCLUSTERED INDEX [UX_Device_UserId_Identifier] CREATE UNIQUE NONCLUSTERED INDEX [UX_Device_UserId_Identifier]
ON [dbo].[Device]([UserId] ASC, [Identifier] ASC); ON [dbo].[Device]([UserId] ASC, [Identifier] ASC);
GO GO
CREATE NONCLUSTERED INDEX [IX_Device_Identifier] CREATE NONCLUSTERED INDEX [IX_Device_Identifier]
ON [dbo].[Device]([Identifier] ASC); ON [dbo].[Device]([Identifier] ASC);

View File

@ -0,0 +1,118 @@
SET DEADLOCK_PRIORITY HIGH
GO
-- add column
IF COL_LENGTH('[dbo].[Device]', 'Active') IS NULL
BEGIN
ALTER TABLE
[dbo].[Device]
ADD
[Active] BIT NOT NULL CONSTRAINT [DF_Device_Active] DEFAULT (1)
END
GO
-- refresh view
CREATE OR ALTER VIEW [dbo].[DeviceView]
AS
SELECT
*
FROM
[dbo].[Device]
GO
-- drop now-unused proc for deletion
IF OBJECT_ID('[dbo].[Device_DeleteById]') IS NOT NULL
BEGIN
DROP PROCEDURE [dbo].[Device_DeleteById]
END
GO
-- refresh procs
CREATE OR ALTER PROCEDURE [dbo].[Device_Create]
@Id UNIQUEIDENTIFIER OUTPUT,
@UserId UNIQUEIDENTIFIER,
@Name NVARCHAR(50),
@Type TINYINT,
@Identifier NVARCHAR(50),
@PushToken NVARCHAR(255),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@EncryptedUserKey VARCHAR(MAX) = NULL,
@EncryptedPublicKey VARCHAR(MAX) = NULL,
@EncryptedPrivateKey VARCHAR(MAX) = NULL,
@Active BIT = 1
AS
BEGIN
SET NOCOUNT ON
INSERT INTO [dbo].[Device]
(
[Id],
[UserId],
[Name],
[Type],
[Identifier],
[PushToken],
[CreationDate],
[RevisionDate],
[EncryptedUserKey],
[EncryptedPublicKey],
[EncryptedPrivateKey],
[Active]
)
VALUES
(
@Id,
@UserId,
@Name,
@Type,
@Identifier,
@PushToken,
@CreationDate,
@RevisionDate,
@EncryptedUserKey,
@EncryptedPublicKey,
@EncryptedPrivateKey,
@Active
)
END
GO
CREATE OR ALTER PROCEDURE [dbo].[Device_Update]
@Id UNIQUEIDENTIFIER,
@UserId UNIQUEIDENTIFIER,
@Name NVARCHAR(50),
@Type TINYINT,
@Identifier NVARCHAR(50),
@PushToken NVARCHAR(255),
@CreationDate DATETIME2(7),
@RevisionDate DATETIME2(7),
@EncryptedUserKey VARCHAR(MAX) = NULL,
@EncryptedPublicKey VARCHAR(MAX) = NULL,
@EncryptedPrivateKey VARCHAR(MAX) = NULL,
@Active BIT = 1
AS
BEGIN
SET NOCOUNT ON
UPDATE
[dbo].[Device]
SET
[UserId] = @UserId,
[Name] = @Name,
[Type] = @Type,
[Identifier] = @Identifier,
[PushToken] = @PushToken,
[CreationDate] = @CreationDate,
[RevisionDate] = @RevisionDate,
[EncryptedUserKey] = @EncryptedUserKey,
[EncryptedPublicKey] = @EncryptedPublicKey,
[EncryptedPrivateKey] = @EncryptedPrivateKey,
[Active] = @Active
WHERE
[Id] = @Id
END
GO
SET DEADLOCK_PRIORITY NORMAL
GO

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.MySqlMigrations.Migrations;
/// <inheritdoc />
public partial class DeviceActivation : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Active",
table: "Device",
type: "tinyint(1)",
nullable: false,
defaultValue: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Active",
table: "Device");
}
}

View File

@ -939,6 +939,10 @@ namespace Bit.MySqlMigrations.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("char(36)"); .HasColumnType("char(36)");
b.Property<bool>("Active")
.HasColumnType("tinyint(1)")
.HasDefaultValue(true);
b.Property<DateTime>("CreationDate") b.Property<DateTime>("CreationDate")
.HasColumnType("datetime(6)"); .HasColumnType("datetime(6)");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.PostgresMigrations.Migrations;
/// <inheritdoc />
public partial class DeviceActivation : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Active",
table: "Device",
type: "boolean",
nullable: false,
defaultValue: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Active",
table: "Device");
}
}

View File

@ -944,6 +944,10 @@ namespace Bit.PostgresMigrations.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<bool>("Active")
.HasColumnType("boolean")
.HasDefaultValue(true);
b.Property<DateTime>("CreationDate") b.Property<DateTime>("CreationDate")
.HasColumnType("timestamp with time zone"); .HasColumnType("timestamp with time zone");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Bit.SqliteMigrations.Migrations;
/// <inheritdoc />
public partial class DeviceActivation : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Active",
table: "Device",
type: "INTEGER",
nullable: false,
defaultValue: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Active",
table: "Device");
}
}

View File

@ -928,6 +928,10 @@ namespace Bit.SqliteMigrations.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<bool>("Active")
.HasColumnType("INTEGER")
.HasDefaultValue(true);
b.Property<DateTime>("CreationDate") b.Property<DateTime>("CreationDate")
.HasColumnType("TEXT"); .HasColumnType("TEXT");