mirror of
https://github.com/bitwarden/server.git
synced 2025-03-02 04:11:04 +01:00
Stored procedure and entity framework query written. Still have cleanup to do and questions to answer.
This commit is contained in:
parent
bca5eff1a8
commit
e916fafd89
@ -72,16 +72,14 @@ public class DevicesController : Controller
|
||||
return response;
|
||||
}
|
||||
|
||||
// working here
|
||||
[HttpGet("")]
|
||||
public async Task<ListResponseModel<DeviceAuthRequestResponseModel>> Get()
|
||||
{
|
||||
var expirationMinutes = _globalSettings.PasswordlessAuth.UserRequestExpiration.TotalMinutes;
|
||||
var devices = await _deviceRepository.GetManyByUserIdWithDeviceAuth(_userService.GetProperUserId(User).Value, (int)expirationMinutes);
|
||||
var responses = devices.Select(d => new DeviceAuthRequestResponseModel(d));
|
||||
return new ListResponseModel<DeviceAuthRequestResponseModel>(responses);
|
||||
var response = new ListResponseModel<DeviceAuthRequestResponseModel>(devices);
|
||||
return response;
|
||||
}
|
||||
// end working here
|
||||
|
||||
[HttpPost("")]
|
||||
public async Task<DeviceResponseModel> Post([FromBody] DeviceRequestModel model)
|
||||
|
@ -1,41 +0,0 @@
|
||||
using Bit.Core.Auth.Utilities;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Api;
|
||||
|
||||
namespace Bit.Api.Models.Response;
|
||||
|
||||
public class DeviceAuthRequestResponseModel : ResponseModel
|
||||
{
|
||||
public DeviceAuthRequestResponseModel(Device device)
|
||||
: base("device")
|
||||
{
|
||||
if (device == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(device));
|
||||
}
|
||||
|
||||
Id = device.Id;
|
||||
Name = device.Name;
|
||||
Type = device.Type;
|
||||
Identifier = device.Identifier;
|
||||
CreationDate = device.CreationDate;
|
||||
IsTrusted = device.IsTrusted();
|
||||
DevicePendingAuthRequest = new PendingAuthRequest();
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public DeviceType Type { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public bool IsTrusted { get; set; }
|
||||
|
||||
public PendingAuthRequest DevicePendingAuthRequest { get; set; }
|
||||
|
||||
public class PendingAuthRequest
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
}
|
||||
}
|
@ -1,5 +1,12 @@
|
||||
namespace Bit.Core.Auth.Enums;
|
||||
|
||||
/**
|
||||
* The type of auth request.
|
||||
*
|
||||
* Note:
|
||||
* Used by the Device_ReadActiveWithPendingAuthRequestsByUserId.sql stored procedure.
|
||||
* If the enum changes be aware of this reference.
|
||||
*/
|
||||
public enum AuthRequestType : byte
|
||||
{
|
||||
AuthenticateAndUnlock = 0,
|
||||
|
@ -0,0 +1,84 @@
|
||||
using Bit.Core.Auth.Utilities;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Api;
|
||||
|
||||
namespace Bit.Core.Auth.Models.Api.Response;
|
||||
|
||||
public class DeviceAuthRequestResponseModel : ResponseModel
|
||||
{
|
||||
public DeviceAuthRequestResponseModel(
|
||||
Device device,
|
||||
Guid authRequestId,
|
||||
DateTime authRequestCreationDate)
|
||||
: base("device")
|
||||
{
|
||||
if (device == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(device));
|
||||
}
|
||||
|
||||
Id = device.Id;
|
||||
Name = device.Name;
|
||||
Type = device.Type;
|
||||
Identifier = device.Identifier;
|
||||
CreationDate = device.CreationDate;
|
||||
IsTrusted = device.IsTrusted();
|
||||
if (authRequestId != Guid.Empty && authRequestCreationDate != DateTime.MinValue)
|
||||
{
|
||||
DevicePendingAuthRequest = new PendingAuthRequest { Id = authRequestId, CreationDate = authRequestCreationDate };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is there a better way to do this in Dapper so that I don't need to explicitly
|
||||
* enumerate all the properties in the constructor for mapping?
|
||||
*/
|
||||
public DeviceAuthRequestResponseModel(
|
||||
Guid id,
|
||||
Guid userId,
|
||||
string name,
|
||||
short type,
|
||||
string identifier,
|
||||
string pushToken,
|
||||
DateTime creationDate,
|
||||
DateTime revisionDate,
|
||||
string encryptedUserKey,
|
||||
string encryptedPublicKey,
|
||||
string encryptedPrivateKey,
|
||||
bool active,
|
||||
Guid authRequestId,
|
||||
DateTime authRequestCreationDate)
|
||||
: base("device")
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Type = (DeviceType)type;
|
||||
Identifier = identifier;
|
||||
CreationDate = creationDate;
|
||||
IsTrusted = active;
|
||||
if (authRequestId != Guid.Empty && authRequestCreationDate != DateTime.MinValue)
|
||||
{
|
||||
DevicePendingAuthRequest = new PendingAuthRequest()
|
||||
{
|
||||
Id = authRequestId,
|
||||
CreationDate = authRequestCreationDate,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public Guid Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public DeviceType Type { get; set; }
|
||||
public string Identifier { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
public bool IsTrusted { get; set; }
|
||||
|
||||
public PendingAuthRequest DevicePendingAuthRequest { get; set; }
|
||||
|
||||
public class PendingAuthRequest
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public DateTime CreationDate { get; set; }
|
||||
}
|
||||
}
|
@ -2,5 +2,4 @@
|
||||
|
||||
public interface IDeviceReadActiveWithPendingAuthRequestsByUserIdQuery
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
namespace Bit.Core.Auth.UserFeatures.DeviceReadActiveWithPendingAuthRequestsByUserId;
|
||||
|
||||
public class DeviceReadActiveWithPendingAuthRequestsByUserIdQuery
|
||||
public class DeviceReadActiveWithPendingAuthRequestsByUserIdQuery : IDeviceReadActiveWithPendingAuthRequestsByUserIdQuery
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Auth.Models.Api.Response;
|
||||
using Bit.Core.Entities;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@ -10,6 +11,6 @@ public interface IDeviceRepository : IRepository<Device, Guid>
|
||||
Task<Device?> GetByIdentifierAsync(string identifier);
|
||||
Task<Device?> GetByIdentifierAsync(string identifier, Guid userId);
|
||||
Task<ICollection<Device>> GetManyByUserIdAsync(Guid userId);
|
||||
Task<ICollection<Device>> GetManyByUserIdWithDeviceAuth(Guid userId, int expirationMinutes);
|
||||
Task<ICollection<DeviceAuthRequestResponseModel>> GetManyByUserIdWithDeviceAuth(Guid userId, int expirationMinutes);
|
||||
Task ClearPushTokenAsync(Guid id);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Data;
|
||||
using Bit.Core.Auth.Models.Api.Response;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
@ -76,11 +77,11 @@ public class DeviceRepository : Repository<Device, Guid>, IDeviceRepository
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<Device>> GetManyByUserIdWithDeviceAuth(Guid userId, int expirationMinutes)
|
||||
public async Task<ICollection<DeviceAuthRequestResponseModel>> GetManyByUserIdWithDeviceAuth(Guid userId, int expirationMinutes)
|
||||
{
|
||||
using (var connection = new SqlConnection(ConnectionString))
|
||||
{
|
||||
var results = await connection.QueryAsync<Device>(
|
||||
var results = await connection.QueryAsync<DeviceAuthRequestResponseModel>(
|
||||
$"[{Schema}].[{Table}_ReadActiveWithPendingAuthRequestsByUserId]",
|
||||
new
|
||||
{
|
||||
|
@ -1,4 +1,6 @@
|
||||
using AutoMapper;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Api.Response;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Infrastructure.EntityFramework.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -70,26 +72,34 @@ public class DeviceRepository : Repository<Core.Entities.Device, Device, Guid>,
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ICollection<Core.Entities.Device>> GetManyByUserIdWithDeviceAuth(Guid userId, int expirationMinutes)
|
||||
public async Task<ICollection<DeviceAuthRequestResponseModel>> GetManyByUserIdWithDeviceAuth(Guid userId, int expirationMinutes)
|
||||
{
|
||||
using (var scope = ServiceScopeFactory.CreateScope())
|
||||
{
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
|
||||
// This is getting closer, need to keep work-shopping it.
|
||||
var currentTime = DateTime.UtcNow;
|
||||
|
||||
var query = from device in dbContext.Devices
|
||||
join authRequest in dbContext.AuthRequests
|
||||
on device.UserId equals authRequest.UserId
|
||||
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) > currentTime)
|
||||
.OrderByDescending(ar => ar.CreationDate)
|
||||
.Take(1)
|
||||
where device.UserId == userId && device.Active == true
|
||||
select new
|
||||
{
|
||||
AuthRequestId = from authRequest in deviceRequests select authRequest.Id,
|
||||
AuthCreationDate = from authRequest in deviceRequests select authRequest.CreationDate,
|
||||
Device = device,
|
||||
AuthRequestId = authRequest.Id,
|
||||
AuthRequestCreationDate = authRequest.CreationDate
|
||||
};
|
||||
|
||||
var devices = await query.ToListAsync();
|
||||
return Mapper.Map<List<Core.Entities.Device>>(devices);
|
||||
return Mapper.Map<List<DeviceAuthRequestResponseModel>>(devices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,16 +3,23 @@ CREATE PROCEDURE [dbo].[Device_ReadActiveWithPendingAuthRequestsByUserId]
|
||||
@ExpirationMinutes INT
|
||||
AS
|
||||
BEGIN
|
||||
SELECT D.*,
|
||||
AR.Id as AuthRequestId,
|
||||
AR.CreationDate as AuthRequestCreationDate
|
||||
SELECT
|
||||
D.*,
|
||||
AR.Id as AuthRequestId,
|
||||
AR.CreationDate as AuthRequestCreationDate
|
||||
FROM [dbo].[DeviceView] D
|
||||
LEFT OUTER JOIN [dbo].[AuthRequestView] AR
|
||||
ON D.userId = AR.userId
|
||||
AND AR.RequestDeviceIdentifier = D.Identifier
|
||||
AND AR.Type IN (0, 1) -- Exclude Admin Approval (type 2)
|
||||
AND DATEADD(mi, @ExpirationMinutes, AR.CreationDate) < GETUTCDATE() -- This means it hasn't expired
|
||||
AND AR.Approved IS NOT NULL -- This means it hasn't been approved already
|
||||
WHERE D.UserId = @UserId
|
||||
AND D.Active = 1 -- Device is active
|
||||
LEFT OUTER JOIN (
|
||||
SELECT TOP 1 -- Take only the top record sorted by auth request creation date
|
||||
AR.Id,
|
||||
AR.CreationDate,
|
||||
AR.RequestDeviceIdentifier
|
||||
FROM [dbo].[AuthRequestView] AR
|
||||
WHERE AR.Type IN (0, 1) -- Include only specific types, excluding Admin Approval (type 2)
|
||||
AND DATEADD(mi, @ExpirationMinutes, AR.CreationDate) > GETUTCDATE() -- Ensure the request hasn't expired
|
||||
AND AR.Approved IS NULL -- Include only requests that haven't been acknowledged or approved
|
||||
ORDER BY AR.CreationDate DESC
|
||||
) AR ON D.Identifier = AR.RequestDeviceIdentifier
|
||||
WHERE
|
||||
D.UserId = @UserId
|
||||
AND D.Active = 1 -- Include only active devices
|
||||
END
|
||||
|
@ -1,18 +0,0 @@
|
||||
CREATE OR ALTER PROCEDURE [dbo].[Device_ReadActiveWithPendingAuthRequestsByUserId]
|
||||
@UserId UNIQUEIDENTIFIER,
|
||||
@ExpirationMinutes INT
|
||||
AS
|
||||
BEGIN
|
||||
SELECT D.*,
|
||||
AR.Id as AuthRequestId,
|
||||
AR.CreationDate as AuthRequestCreationDate
|
||||
FROM [dbo].[DeviceView] D
|
||||
LEFT OUTER JOIN [dbo].[AuthRequestView] AR
|
||||
ON D.userId = AR.userId
|
||||
AND AR.RequestDeviceIdentifier = D.Identifier
|
||||
AND AR.Type IN (0, 1) -- Exclude Admin Approval (type 2)
|
||||
AND DATEADD(mi, @ExpirationMinutes, AR.CreationDate) < GETUTCDATE() -- This means it hasn't expired
|
||||
AND AR.Approved IS NOT NULL -- This means it hasn't been approved already
|
||||
WHERE D.UserId = @UserId
|
||||
AND D.Active = 1 -- Device is active
|
||||
END
|
@ -0,0 +1,25 @@
|
||||
CREATE PROCEDURE [dbo].[Device_ReadActiveWithPendingAuthRequestsByUserId]
|
||||
@UserId UNIQUEIDENTIFIER,
|
||||
@ExpirationMinutes INT
|
||||
AS
|
||||
BEGIN
|
||||
SELECT
|
||||
D.*,
|
||||
AR.Id as AuthRequestId,
|
||||
AR.CreationDate as AuthRequestCreationDate
|
||||
FROM [dbo].[DeviceView] D
|
||||
LEFT OUTER JOIN (
|
||||
SELECT TOP 1 -- Take only the top record sorted by auth request creation date
|
||||
AR.Id,
|
||||
AR.CreationDate,
|
||||
AR.RequestDeviceIdentifier
|
||||
FROM [dbo].[AuthRequestView] AR
|
||||
WHERE AR.Type IN (0, 1) -- Include only specific types, excluding Admin Approval (type 2)
|
||||
AND DATEADD(mi, @ExpirationMinutes, AR.CreationDate) > GETUTCDATE() -- Ensure the request hasn't expired
|
||||
AND AR.Approved IS NULL -- Include only requests that haven't been acknowledged or approved
|
||||
ORDER BY AR.CreationDate DESC
|
||||
) AR ON D.Identifier = AR.RequestDeviceIdentifier
|
||||
WHERE
|
||||
D.UserId = @UserId
|
||||
AND D.Active = 1 -- Include only active devices
|
||||
END
|
Loading…
Reference in New Issue
Block a user