1
0
mirror of https://github.com/bitwarden/server synced 2026-01-11 13:03:27 +00:00
Files
server/src/Infrastructure.EntityFramework/Repositories/DeviceRepository.cs
Bernd Schoolmann 8fd48374dc [PM-2199] Implement userkey rotation for all TDE devices (#5446)
* Implement userkey rotation v2

* Update request models

* Cleanup

* Update tests

* Improve test

* Add tests

* Fix formatting

* Fix test

* Remove whitespace

* Fix namespace

* Enable nullable on models

* Fix build

* Add tests and enable nullable on masterpasswordunlockdatamodel

* Fix test

* Remove rollback

* Add tests

* Make masterpassword hint optional

* Update user query

* Add EF test

* Improve test

* Cleanup

* Set masterpassword hint

* Remove connection close

* Add tests for invalid kdf types

* Update test/Core.Test/KeyManagement/UserKey/RotateUserAccountKeysCommandTests.cs

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Fix formatting

* Update src/Api/KeyManagement/Models/Requests/RotateAccountKeysAndDataRequestModel.cs

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update src/Api/Auth/Models/Request/Accounts/MasterPasswordUnlockDataModel.cs

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update src/Api/Auth/Models/Request/Accounts/MasterPasswordUnlockDataModel.cs

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update src/Api/KeyManagement/Models/Requests/AccountKeysRequestModel.cs

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Fix imports

* Fix tests

* Add poc for tde rotation

* Improve rotation transaction safety

* Add validator tests

* Clean up validator

* Add newline

* Add devicekey unlock data to integration test

* Fix tests

* Fix tests

* Remove null check

* Remove null check

* Fix IsTrusted returning wrong result

* Add rollback

* Cleanup

* Address feedback

* Further renames

---------

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>
2025-04-03 11:30:49 +02:00

122 lines
4.5 KiB
C#

using AutoMapper;
using Bit.Core.Auth.Models.Data;
using Bit.Core.KeyManagement.UserKey;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Infrastructure.EntityFramework.Auth.Repositories.Queries;
using Bit.Infrastructure.EntityFramework.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
#nullable enable
namespace Bit.Infrastructure.EntityFramework.Repositories;
public class DeviceRepository : Repository<Core.Entities.Device, Device, Guid>, IDeviceRepository
{
private readonly IGlobalSettings _globalSettings;
public DeviceRepository(
IServiceScopeFactory serviceScopeFactory,
IMapper mapper,
IGlobalSettings globalSettings
)
: base(serviceScopeFactory, mapper, (DatabaseContext context) => context.Devices)
{
_globalSettings = globalSettings;
}
public async Task ClearPushTokenAsync(Guid id)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var query = dbContext.Devices.Where(d => d.Id == id);
dbContext.AttachRange(query);
await query.ForEachAsync(x => x.PushToken = null);
await dbContext.SaveChangesAsync();
}
}
public async Task<Core.Entities.Device?> GetByIdAsync(Guid id, Guid userId)
{
var device = await base.GetByIdAsync(id);
if (device == null || device.UserId != userId)
{
return null;
}
return Mapper.Map<Core.Entities.Device>(device);
}
public async Task<Core.Entities.Device?> GetByIdentifierAsync(string identifier)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var query = dbContext.Devices.Where(d => d.Identifier == identifier);
var device = await query.FirstOrDefaultAsync();
return Mapper.Map<Core.Entities.Device>(device);
}
}
public async Task<Core.Entities.Device?> GetByIdentifierAsync(string identifier, Guid userId)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var query = dbContext.Devices.Where(d => d.Identifier == identifier && d.UserId == userId);
var device = await query.FirstOrDefaultAsync();
return Mapper.Map<Core.Entities.Device>(device);
}
}
public async Task<ICollection<Core.Entities.Device>> GetManyByUserIdAsync(Guid userId)
{
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var query = dbContext.Devices.Where(d => d.UserId == userId);
var devices = await query.ToListAsync();
return Mapper.Map<List<Core.Entities.Device>>(devices);
}
}
public async Task<ICollection<DeviceAuthDetails>> GetManyByUserIdWithDeviceAuth(Guid userId)
{
var expirationMinutes = (int)_globalSettings.PasswordlessAuth.UserRequestExpiration.TotalMinutes;
using (var scope = ServiceScopeFactory.CreateScope())
{
var dbContext = GetDatabaseContext(scope);
var query = new DeviceWithPendingAuthByUserIdQuery();
return await query.GetQuery(dbContext, userId, expirationMinutes).ToListAsync();
}
}
public UpdateEncryptedDataForKeyRotation UpdateKeysForRotationAsync(Guid userId, IEnumerable<Core.Entities.Device> devices)
{
return async (_, _) =>
{
var deviceUpdates = devices.ToList();
using var scope = ServiceScopeFactory.CreateScope();
var dbContext = GetDatabaseContext(scope);
var userDevices = await GetDbSet(dbContext)
.Where(device => device.UserId == userId)
.ToListAsync();
var userDevicesWithUpdatesPending = userDevices
.Where(existingDevice => deviceUpdates.Any(updatedDevice => updatedDevice.Id == existingDevice.Id))
.ToList();
foreach (var deviceToUpdate in userDevicesWithUpdatesPending)
{
var deviceUpdate = deviceUpdates.First(deviceUpdate => deviceUpdate.Id == deviceToUpdate.Id);
deviceToUpdate.EncryptedPublicKey = deviceUpdate.EncryptedPublicKey;
deviceToUpdate.EncryptedUserKey = deviceUpdate.EncryptedUserKey;
}
await dbContext.SaveChangesAsync();
};
}
}