From 23c049a6178d90101c0112fe184f66afbc8c8264 Mon Sep 17 00:00:00 2001 From: Patrick Pimentel Date: Mon, 9 Dec 2024 12:18:56 -0500 Subject: [PATCH] Optimized implementations of sqlite and other db interactions. --- .../DeviceAuthRequestResponseModel.cs | 7 +- .../Repositories/DeviceRepository.cs | 13 ++++ .../Models/DeviceAuthRequestResponseModel.cs | 12 --- .../DeviceWithPendingAuthByUserIdQuery.cs | 73 +++++++++++++++++++ .../Repositories/DeviceRepository.cs | 27 +------ 5 files changed, 94 insertions(+), 38 deletions(-) delete mode 100644 src/Infrastructure.EntityFramework/Auth/Models/DeviceAuthRequestResponseModel.cs create mode 100644 src/Infrastructure.EntityFramework/Auth/Repositories/Queries/DeviceWithPendingAuthByUserIdQuery.cs diff --git a/src/Core/Auth/Models/Api/Response/DeviceAuthRequestResponseModel.cs b/src/Core/Auth/Models/Api/Response/DeviceAuthRequestResponseModel.cs index 2e8efbaf12..4c11e027d4 100644 --- a/src/Core/Auth/Models/Api/Response/DeviceAuthRequestResponseModel.cs +++ b/src/Core/Auth/Models/Api/Response/DeviceAuthRequestResponseModel.cs @@ -31,7 +31,7 @@ public class DeviceAuthRequestResponseModel : ResponseModel } /** - * Is there a better way to do this in Dapper so that I don't need to explicitly + * Is there a better way to do this for Dapper so that I don't need to explicitly * enumerate all the properties in the constructor for mapping? */ public DeviceAuthRequestResponseModel( @@ -56,7 +56,9 @@ public class DeviceAuthRequestResponseModel : ResponseModel Type = (DeviceType)type; Identifier = identifier; CreationDate = creationDate; - IsTrusted = active; + IsTrusted = !string.IsNullOrEmpty(encryptedUserKey) && + !string.IsNullOrEmpty(encryptedPublicKey) && + !string.IsNullOrEmpty(encryptedPrivateKey); if (authRequestId != Guid.Empty && authRequestCreationDate != DateTime.MinValue) { DevicePendingAuthRequest = new PendingAuthRequest() @@ -67,6 +69,7 @@ public class DeviceAuthRequestResponseModel : ResponseModel } } + public Guid Id { get; set; } public string Name { get; set; } public DeviceType Type { get; set; } diff --git a/src/Infrastructure.Dapper/Repositories/DeviceRepository.cs b/src/Infrastructure.Dapper/Repositories/DeviceRepository.cs index 009bb84ff9..7db495d927 100644 --- a/src/Infrastructure.Dapper/Repositories/DeviceRepository.cs +++ b/src/Infrastructure.Dapper/Repositories/DeviceRepository.cs @@ -90,6 +90,19 @@ public class DeviceRepository : Repository, IDeviceRepository }, commandType: CommandType.StoredProcedure); + foreach (var result in results) + { + result.IsTrusted = result.IsTrusted; + if (result.DevicePendingAuthRequest != null) + { + result.DevicePendingAuthRequest = new DeviceAuthRequestResponseModel.PendingAuthRequest + { + Id = result.DevicePendingAuthRequest.Id, + CreationDate = result.DevicePendingAuthRequest.CreationDate + }; + } + } + return results.ToList(); } } diff --git a/src/Infrastructure.EntityFramework/Auth/Models/DeviceAuthRequestResponseModel.cs b/src/Infrastructure.EntityFramework/Auth/Models/DeviceAuthRequestResponseModel.cs deleted file mode 100644 index bb99a6d5c9..0000000000 --- a/src/Infrastructure.EntityFramework/Auth/Models/DeviceAuthRequestResponseModel.cs +++ /dev/null @@ -1,12 +0,0 @@ -using AutoMapper; -using Bit.Core.Auth.Models.Api.Response; - -namespace Bit.Infrastructure.EntityFramework.Auth.Models; - -public class DeviceAuthRequestResponseModelMapperProfile : Profile -{ - public DeviceAuthRequestResponseModelMapperProfile() - { - CreateMap().ReverseMap(); - } -} diff --git a/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/DeviceWithPendingAuthByUserIdQuery.cs b/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/DeviceWithPendingAuthByUserIdQuery.cs new file mode 100644 index 0000000000..4b86fb0675 --- /dev/null +++ b/src/Infrastructure.EntityFramework/Auth/Repositories/Queries/DeviceWithPendingAuthByUserIdQuery.cs @@ -0,0 +1,73 @@ +using Bit.Core.Auth.Enums; +using Bit.Core.Auth.Models.Api.Response; +using Bit.Infrastructure.EntityFramework.Repositories; +using Microsoft.EntityFrameworkCore; + +namespace Bit.Infrastructure.EntityFramework.Auth.Repositories.Queries; + +public class DeviceWithPendingAuthByUserIdQuery +{ + public IQueryable GetQuery( + DatabaseContext dbContext, + Guid userId, + int expirationMinutes) + { + // Handle sqlite differently because it cannot convert some linq queries into sql syntax. + // This error is thrown when trying to run the join else clause below with sqlite: + // 'Translating this query requires the SQL APPLY operation, which is not supported on SQLite.' + if (dbContext.Database.IsSqlite()) + { + var query = + (from device in dbContext.Devices + where device.UserId == userId && device.Active + select new + { + device, + authRequest = + (from authRequest in dbContext.AuthRequests + where authRequest.RequestDeviceIdentifier == device.Identifier + where authRequest.Type == AuthRequestType.AuthenticateAndUnlock || + authRequest.Type == AuthRequestType.Unlock + where authRequest.Approved == null + where authRequest.UserId == userId + where authRequest.CreationDate.AddMinutes(expirationMinutes) > DateTime.UtcNow + orderby authRequest.CreationDate descending + select authRequest).First() + } + ).Select(deviceWithAuthRequest => new DeviceAuthRequestResponseModel( + deviceWithAuthRequest.device, + deviceWithAuthRequest.authRequest != null ? deviceWithAuthRequest.authRequest.Id : Guid.Empty, + deviceWithAuthRequest.authRequest != null + ? deviceWithAuthRequest.authRequest.CreationDate + : DateTime.MinValue)); + + return query; + } + else + { + var query = + from device in dbContext.Devices + join authRequest in dbContext.AuthRequests + on device.Identifier equals authRequest.RequestDeviceIdentifier + into deviceRequests + from authRequest in deviceRequests + .Where(ar => ar.Type == AuthRequestType.AuthenticateAndUnlock || ar.Type == AuthRequestType.Unlock) + .Where(ar => ar.Approved == null) + .Where(ar => ar.CreationDate.AddMinutes(expirationMinutes) > DateTime.UtcNow) + .OrderByDescending(ar => ar.CreationDate) + .Take(1) + where device.UserId == userId && device.Active == true + select new + { + Device = device, + AuthRequestId = authRequest.Id, + AuthRequestCreationDate = authRequest.CreationDate + }; + + var devicesWithAuthQuery = query.Select(deviceAndAuthProperty => new DeviceAuthRequestResponseModel( + deviceAndAuthProperty.Device, deviceAndAuthProperty.AuthRequestId, deviceAndAuthProperty.AuthRequestCreationDate)); + + return devicesWithAuthQuery; + } + } +} diff --git a/src/Infrastructure.EntityFramework/Repositories/DeviceRepository.cs b/src/Infrastructure.EntityFramework/Repositories/DeviceRepository.cs index fd44c35f97..4d11b24be2 100644 --- a/src/Infrastructure.EntityFramework/Repositories/DeviceRepository.cs +++ b/src/Infrastructure.EntityFramework/Repositories/DeviceRepository.cs @@ -1,7 +1,7 @@ using AutoMapper; -using Bit.Core.Auth.Enums; using Bit.Core.Auth.Models.Api.Response; using Bit.Core.Repositories; +using Bit.Infrastructure.EntityFramework.Auth.Repositories.Queries; using Bit.Infrastructure.EntityFramework.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; @@ -77,29 +77,8 @@ public class DeviceRepository : Repository, using (var scope = ServiceScopeFactory.CreateScope()) { var dbContext = GetDatabaseContext(scope); - - var query = from device in dbContext.Devices - where device.UserId == userId && device.Active - select new - { - device, - authRequest = (from authRequest in dbContext.AuthRequests - where authRequest.RequestDeviceIdentifier == device.Identifier - where authRequest.Type == AuthRequestType.AuthenticateAndUnlock || authRequest.Type == AuthRequestType.Unlock - where authRequest.Approved == null - where authRequest.UserId == userId - where authRequest.CreationDate.AddMinutes(expirationMinutes) > DateTime.UtcNow - orderby authRequest.CreationDate descending - select authRequest).First() - }; - - var devices = - await query.Select(deviceWithAuthRequest => new DeviceAuthRequestResponseModel( - deviceWithAuthRequest.device, - deviceWithAuthRequest.authRequest != null ? deviceWithAuthRequest.authRequest.Id : Guid.Empty, - deviceWithAuthRequest.authRequest != null ? deviceWithAuthRequest.authRequest.CreationDate : DateTime.MinValue)).ToListAsync(); - - return devices; + var query = new DeviceWithPendingAuthByUserIdQuery(); + return await query.GetQuery(dbContext, userId, expirationMinutes).ToListAsync(); } } }