mirror of
https://github.com/bitwarden/server.git
synced 2025-06-15 07:20:49 -05:00
feat: add deviceId to response for pending auth requests; Modified queries to match join for new devices; Fixed tests to account for object changes.
This commit is contained in:
parent
d525a1c93c
commit
f944bd2a31
@ -52,12 +52,12 @@ public class AuthRequestsController(
|
||||
|
||||
[HttpGet("pending")]
|
||||
[RequireFeature(FeatureFlagKeys.BrowserExtensionLoginApproval)]
|
||||
public async Task<ListResponseModel<AuthRequestResponseModel>> GetPendingAuthRequestsAsync()
|
||||
public async Task<ListResponseModel<PendingAuthRequestResponseModel>> GetPendingAuthRequestsAsync()
|
||||
{
|
||||
var userId = _userService.GetProperUserId(User).Value;
|
||||
var rawResponse = await _authRequestRepository.GetManyPendingAuthRequestByUserId(userId);
|
||||
var responses = rawResponse.Select(a => new AuthRequestResponseModel(a, _globalSettings.BaseServiceUri.Vault));
|
||||
return new ListResponseModel<AuthRequestResponseModel>(responses);
|
||||
var responses = rawResponse.Select(a => new PendingAuthRequestResponseModel(a, _globalSettings.BaseServiceUri.Vault));
|
||||
return new ListResponseModel<PendingAuthRequestResponseModel>(responses);
|
||||
}
|
||||
|
||||
[HttpGet("{id}/response")]
|
||||
|
@ -0,0 +1,15 @@
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
|
||||
namespace Bit.Api.Auth.Models.Response;
|
||||
|
||||
public class PendingAuthRequestResponseModel : AuthRequestResponseModel
|
||||
{
|
||||
public PendingAuthRequestResponseModel(PendingAuthRequestDetails authRequest, string vaultUri, string obj = "auth-request")
|
||||
: base(authRequest, vaultUri, obj)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(authRequest);
|
||||
RequestingDeviceId = authRequest.DeviceId;
|
||||
}
|
||||
|
||||
public Guid? RequestingDeviceId { get; set; }
|
||||
}
|
89
src/Core/Auth/Models/Data/PendingAuthRequestDetails.cs
Normal file
89
src/Core/Auth/Models/Data/PendingAuthRequestDetails.cs
Normal file
@ -0,0 +1,89 @@
|
||||
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.Auth.Models.Data;
|
||||
|
||||
public class PendingAuthRequestDetails : AuthRequest
|
||||
{
|
||||
public Guid? DeviceId { get; set; }
|
||||
|
||||
/**
|
||||
* Constructor for EF response.
|
||||
*/
|
||||
public PendingAuthRequestDetails(
|
||||
AuthRequest authRequest,
|
||||
Guid? deviceId)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(authRequest);
|
||||
|
||||
Id = authRequest.Id;
|
||||
UserId = authRequest.UserId;
|
||||
OrganizationId = authRequest.OrganizationId;
|
||||
Type = authRequest.Type;
|
||||
RequestDeviceIdentifier = authRequest.RequestDeviceIdentifier;
|
||||
RequestDeviceType = authRequest.RequestDeviceType;
|
||||
RequestIpAddress = authRequest.RequestIpAddress;
|
||||
RequestCountryName = authRequest.RequestCountryName;
|
||||
ResponseDeviceId = authRequest.ResponseDeviceId;
|
||||
AccessCode = authRequest.AccessCode;
|
||||
PublicKey = authRequest.PublicKey;
|
||||
Key = authRequest.Key;
|
||||
MasterPasswordHash = authRequest.MasterPasswordHash;
|
||||
Approved = authRequest.Approved;
|
||||
CreationDate = authRequest.CreationDate;
|
||||
ResponseDate = authRequest.ResponseDate;
|
||||
AuthenticationDate = authRequest.AuthenticationDate;
|
||||
DeviceId = deviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for dapper response.
|
||||
* Note: if the DeviceId is null it comes back as an empty guid That could change if the stored
|
||||
* procedure runs on a different kind of db.
|
||||
* In order to maintain the flexibility of the wildcard * in SQL the constrctor accepts a long "row number rn"
|
||||
* parameter that was used to order the results in the SQL query. Also SQL complains about the constructor not
|
||||
* having the same parameters as the SELECT statement.
|
||||
*/
|
||||
public PendingAuthRequestDetails(
|
||||
Guid id,
|
||||
Guid userId,
|
||||
short type,
|
||||
string requestDeviceIdentifier,
|
||||
short requestDeviceType,
|
||||
string requestIpAddress,
|
||||
Guid? responseDeviceId,
|
||||
string accessCode,
|
||||
string publicKey,
|
||||
string key,
|
||||
string masterPasswordHash,
|
||||
DateTime creationDate,
|
||||
DateTime? responseDate,
|
||||
DateTime? authenticationDate,
|
||||
bool? approved,
|
||||
Guid organizationId,
|
||||
string requestCountryName,
|
||||
Guid deviceId,
|
||||
long rn) // see comment above about rn parameter
|
||||
{
|
||||
Id = id;
|
||||
UserId = userId;
|
||||
OrganizationId = organizationId;
|
||||
Type = (AuthRequestType)type;
|
||||
RequestDeviceIdentifier = requestDeviceIdentifier;
|
||||
RequestDeviceType = (DeviceType)requestDeviceType;
|
||||
RequestIpAddress = requestIpAddress;
|
||||
RequestCountryName = requestCountryName;
|
||||
ResponseDeviceId = responseDeviceId;
|
||||
AccessCode = accessCode;
|
||||
PublicKey = publicKey;
|
||||
Key = key;
|
||||
MasterPasswordHash = masterPasswordHash;
|
||||
Approved = approved;
|
||||
CreationDate = creationDate;
|
||||
ResponseDate = responseDate;
|
||||
AuthenticationDate = authenticationDate;
|
||||
DeviceId = deviceId;
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ public interface IAuthRequestRepository : IRepository<AuthRequest, Guid>
|
||||
/// </summary>
|
||||
/// <param name="userId">UserId of the owner of the AuthRequests</param>
|
||||
/// <returns>a collection Auth request details or null</returns>
|
||||
Task<IEnumerable<AuthRequest>> GetManyPendingAuthRequestByUserId(Guid userId);
|
||||
Task<IEnumerable<PendingAuthRequestDetails>> GetManyPendingAuthRequestByUserId(Guid userId);
|
||||
Task<ICollection<OrganizationAdminAuthRequest>> GetManyPendingByOrganizationIdAsync(Guid organizationId);
|
||||
Task<ICollection<OrganizationAdminAuthRequest>> GetManyAdminApprovalRequestsByManyIdsAsync(Guid organizationId, IEnumerable<Guid> ids);
|
||||
Task UpdateManyAsync(IEnumerable<AuthRequest> authRequests);
|
||||
|
@ -51,11 +51,11 @@ public class AuthRequestRepository : Repository<AuthRequest, Guid>, IAuthRequest
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AuthRequest>> GetManyPendingAuthRequestByUserId(Guid userId)
|
||||
public async Task<IEnumerable<PendingAuthRequestDetails>> GetManyPendingAuthRequestByUserId(Guid userId)
|
||||
{
|
||||
var expirationMinutes = (int)_globalSettings.PasswordlessAuth.UserRequestExpiration.TotalMinutes;
|
||||
using var connection = new SqlConnection(ConnectionString);
|
||||
var results = await connection.QueryAsync<OrganizationAdminAuthRequest>(
|
||||
var results = await connection.QueryAsync<PendingAuthRequestDetails>(
|
||||
$"[{Schema}].[AuthRequest_ReadPendingByUserId]",
|
||||
new { UserId = userId, ExpirationMinutes = expirationMinutes },
|
||||
commandType: CommandType.StoredProcedure);
|
||||
|
@ -62,7 +62,7 @@ public class AuthRequestRepository : Repository<Core.Auth.Entities.AuthRequest,
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Core.Auth.Entities.AuthRequest>> GetManyPendingAuthRequestByUserId(Guid userId)
|
||||
public async Task<IEnumerable<PendingAuthRequestDetails>> GetManyPendingAuthRequestByUserId(Guid userId)
|
||||
{
|
||||
var expirationMinutes = (int)_globalSettings.PasswordlessAuth.UserRequestExpiration.TotalMinutes;
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
@ -76,10 +76,12 @@ public class AuthRequestRepository : Repository<Core.Auth.Entities.AuthRequest,
|
||||
group authRequest by authRequest.RequestDeviceIdentifier into groupedAuthRequests
|
||||
select
|
||||
(from r in groupedAuthRequests
|
||||
join d in dbContext.Devices on r.RequestDeviceIdentifier equals d.Identifier into deviceJoin
|
||||
from dj in deviceJoin.DefaultIfEmpty() // This accomplishes a left join allowing nulld for devices
|
||||
orderby r.CreationDate descending
|
||||
select r).First()).ToListAsync();
|
||||
select new PendingAuthRequestDetails(r, dj.Id)).First()
|
||||
).ToListAsync();
|
||||
|
||||
// Pending AuthRequests are those where Approved is null.
|
||||
mostRecentAuthRequests.RemoveAll(a => a.Approved != null);
|
||||
|
||||
return mostRecentAuthRequests;
|
||||
|
@ -1,4 +1,4 @@
|
||||
CREATE PROCEDURE [dbo].[AuthRequest_ReadPendingByUserId]
|
||||
CREATE PROCEDURE [dbo].[AuthRequest_ReadPendingByUserId]
|
||||
@UserId UNIQUEIDENTIFIER,
|
||||
@ExpirationMinutes INT
|
||||
AS
|
||||
@ -8,9 +8,12 @@ BEGIN
|
||||
;WITH PendingRequests AS (
|
||||
SELECT
|
||||
AR.*,
|
||||
ROW_NUMBER() OVER (PARTITION BY RequestDeviceIdentifier ORDER BY CreationDate DESC) AS rn
|
||||
D.Id AS DeviceId,
|
||||
ROW_NUMBER() OVER (PARTITION BY AR.RequestDeviceIdentifier ORDER BY AR.CreationDate DESC) AS rn
|
||||
FROM dbo.AuthRequestView AR
|
||||
WHERE Type IN (0, 1) -- 0 = AuthenticateAndUnlock, 1 = Unlock
|
||||
LEFT JOIN
|
||||
Device D ON AR.RequestDeviceIdentifier = D.Identifier
|
||||
WHERE AR.Type IN (0, 1) -- 0 = AuthenticateAndUnlock, 1 = Unlock
|
||||
AND AR.CreationDate >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE())
|
||||
AND AR.UserId = @UserId
|
||||
)
|
@ -5,6 +5,7 @@ using Bit.Api.Models.Response;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Api.Request.AuthRequest;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -100,7 +101,7 @@ public class AuthRequestsControllerTests
|
||||
public async Task GetPending_ReturnsExpectedResult(
|
||||
SutProvider<AuthRequestsController> sutProvider,
|
||||
User user,
|
||||
AuthRequest authRequest)
|
||||
PendingAuthRequestDetails authRequest)
|
||||
{
|
||||
// Arrange
|
||||
SetBaseServiceUri(sutProvider);
|
||||
@ -120,7 +121,7 @@ public class AuthRequestsControllerTests
|
||||
Assert.NotNull(result);
|
||||
var expectedCount = 1;
|
||||
Assert.Equal(result.Data.Count(), expectedCount);
|
||||
Assert.IsType<ListResponseModel<AuthRequestResponseModel>>(result);
|
||||
Assert.IsType<ListResponseModel<PendingAuthRequestResponseModel>>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
|
@ -9,9 +9,12 @@ BEGIN
|
||||
;WITH PendingRequests AS (
|
||||
SELECT
|
||||
AR.*,
|
||||
ROW_NUMBER() OVER (PARTITION BY RequestDeviceIdentifier ORDER BY CreationDate DESC) AS rn
|
||||
D.Id AS DeviceId,
|
||||
ROW_NUMBER() OVER (PARTITION BY AR.RequestDeviceIdentifier ORDER BY AR.CreationDate DESC) AS rn
|
||||
FROM dbo.AuthRequestView AR
|
||||
WHERE Type IN (0, 1) -- 0 = AuthenticateAndUnlock, 1 = Unlock
|
||||
LEFT JOIN
|
||||
Device D ON AR.RequestDeviceIdentifier = D.Identifier
|
||||
WHERE AR.Type IN (0, 1) -- 0 = AuthenticateAndUnlock, 1 = Unlock
|
||||
AND AR.CreationDate >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE())
|
||||
AND AR.UserId = @UserId
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user