Skip to content

Commit 7e5ca36

Browse files
PM-34130 - Fix DeviceAuthDetails constructor and stored procedure for EDD compliance
Replace positional 14-arg Dapper constructor with parameterless constructor and property-setter mapping; rename AuthRequestCreatedAt to AuthRequestCreationDate; convert IsTrusted to a computed property; update stored procedure to use explicit column list instead of SELECT D.* for EDD-safe name-based Dapper mapping; add migration script; expand integration tests for full field mapping, IsTrusted logic, Unlock type eligibility, inactive device exclusion, and empty device list.
1 parent 226828f commit 7e5ca36

5 files changed

Lines changed: 307 additions & 90 deletions

File tree

src/Core/Auth/Models/Api/Response/DeviceAuthRequestResponseModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ public static DeviceAuthRequestResponseModel From(DeviceAuthDetails deviceAuthDe
2727
EncryptedUserKey = deviceAuthDetails.EncryptedUserKey
2828
};
2929

30-
if (deviceAuthDetails.AuthRequestId != null && deviceAuthDetails.AuthRequestCreatedAt != null)
30+
if (deviceAuthDetails.AuthRequestId != null && deviceAuthDetails.AuthRequestCreationDate != null)
3131
{
3232
converted.DevicePendingAuthRequest = new PendingAuthRequest
3333
{
3434
Id = (Guid)deviceAuthDetails.AuthRequestId,
35-
CreationDate = (DateTime)deviceAuthDetails.AuthRequestCreatedAt
35+
CreationDate = (DateTime)deviceAuthDetails.AuthRequestCreationDate
3636
};
3737
}
3838

Lines changed: 8 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
using Bit.Core.Auth.Utilities;
22
using Bit.Core.Entities;
3-
using Bit.Core.Enums;
43

54
namespace Bit.Core.Auth.Models.Data;
65

76
public class DeviceAuthDetails : Device
87
{
9-
public bool IsTrusted { get; set; }
8+
public bool IsTrusted => DeviceExtensions.IsTrusted(this);
109
public Guid? AuthRequestId { get; set; }
11-
public DateTime? AuthRequestCreatedAt { get; set; }
10+
public DateTime? AuthRequestCreationDate { get; set; }
11+
12+
/**
13+
* Parameterless constructor for Dapper name-based mapping.
14+
*/
15+
public DeviceAuthDetails() { }
1216

1317
/**
1418
* Constructor for EF response.
@@ -28,58 +32,9 @@ public DeviceAuthDetails(
2832
Type = device.Type;
2933
Identifier = device.Identifier;
3034
CreationDate = device.CreationDate;
31-
IsTrusted = device.IsTrusted();
3235
EncryptedPublicKey = device.EncryptedPublicKey;
3336
EncryptedUserKey = device.EncryptedUserKey;
3437
AuthRequestId = authRequestId;
35-
AuthRequestCreatedAt = authRequestCreationDate;
36-
}
37-
38-
/**
39-
* Constructor for dapper response.
40-
* Note: if the authRequestId or authRequestCreationDate is null it comes back as
41-
* an empty guid and a min value for datetime. That could change if the stored
42-
* procedure runs on a different kind of db.
43-
*/
44-
public DeviceAuthDetails(
45-
Guid id,
46-
Guid userId,
47-
string name,
48-
short type,
49-
string identifier,
50-
string pushToken,
51-
DateTime creationDate,
52-
DateTime revisionDate,
53-
string encryptedUserKey,
54-
string encryptedPublicKey,
55-
string encryptedPrivateKey,
56-
bool active,
57-
Guid authRequestId,
58-
DateTime authRequestCreationDate)
59-
{
60-
Id = id;
61-
Name = name;
62-
Type = (DeviceType)type;
63-
Identifier = identifier;
64-
CreationDate = creationDate;
65-
IsTrusted = new Device
66-
{
67-
Id = id,
68-
UserId = userId,
69-
Name = name,
70-
Type = (DeviceType)type,
71-
Identifier = identifier,
72-
PushToken = pushToken,
73-
RevisionDate = revisionDate,
74-
EncryptedUserKey = encryptedUserKey,
75-
EncryptedPublicKey = encryptedPublicKey,
76-
EncryptedPrivateKey = encryptedPrivateKey,
77-
Active = active
78-
}.IsTrusted();
79-
EncryptedPublicKey = encryptedPublicKey;
80-
EncryptedUserKey = encryptedUserKey;
81-
AuthRequestId = authRequestId != Guid.Empty ? authRequestId : null;
82-
AuthRequestCreatedAt =
83-
authRequestCreationDate != DateTime.MinValue ? authRequestCreationDate : null;
38+
AuthRequestCreationDate = authRequestCreationDate;
8439
}
8540
}

src/Sql/dbo/Auth/Stored Procedures/Device_ReadActiveWithPendingAuthRequestsByUserId.sql

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,35 @@ BEGIN
66
SET NOCOUNT ON;
77

88
SELECT
9-
D.*,
10-
AR.Id as AuthRequestId,
11-
AR.CreationDate as AuthRequestCreationDate
12-
FROM dbo.DeviceView D
13-
LEFT JOIN (
14-
SELECT
15-
Id,
16-
CreationDate,
17-
RequestDeviceIdentifier,
18-
Approved,
19-
ROW_NUMBER() OVER (PARTITION BY RequestDeviceIdentifier ORDER BY CreationDate DESC) as rn
20-
FROM dbo.AuthRequestView
21-
WHERE Type IN (0, 1) -- AuthenticateAndUnlock and Unlock types only
22-
AND CreationDate >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE()) -- Ensure the request hasn't expired
23-
AND UserId = @UserId -- Requests for this user only
24-
) AR -- This join will get the most recent request per device, regardless of approval status
25-
ON D.Identifier = AR.RequestDeviceIdentifier AND AR.rn = 1 AND AR.Approved IS NULL -- Get only the most recent unapproved request per device
9+
D.[Id],
10+
D.[UserId],
11+
D.[Name],
12+
D.[Type],
13+
D.[Identifier],
14+
D.[PushToken],
15+
D.[CreationDate],
16+
D.[RevisionDate],
17+
D.[EncryptedUserKey],
18+
D.[EncryptedPublicKey],
19+
D.[EncryptedPrivateKey],
20+
D.[Active],
21+
AR.[Id] AS [AuthRequestId],
22+
AR.[CreationDate] AS [AuthRequestCreationDate]
23+
FROM [dbo].[DeviceView] D
24+
LEFT OUTER JOIN (
25+
SELECT
26+
[Id],
27+
[CreationDate],
28+
[RequestDeviceIdentifier],
29+
[Approved],
30+
ROW_NUMBER() OVER (PARTITION BY [RequestDeviceIdentifier] ORDER BY [CreationDate] DESC) AS rn
31+
FROM [dbo].[AuthRequestView]
32+
WHERE [Type] IN (0,1) -- AuthenticateAndUnlock and Unlock types only
33+
AND [CreationDate] >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE()) -- Ensure the request hasn't expired
34+
AND [UserId] = @UserId -- Requests for this user only
35+
) AR -- This join will get the most recent request per device, regardless of approval status
36+
ON D.[Identifier] = AR.[RequestDeviceIdentifier] AND AR.[rn] = 1 AND AR.[Approved] IS NULL -- Get only the most recent unapproved request per device
2637
WHERE
27-
D.UserId = @UserId -- Include only devices for this user
28-
AND D.Active = 1; -- Include only active devices
38+
D.[UserId] = @UserId -- Include only devices for this user
39+
AND D.[Active] = 1; -- Include only active devices
2940
END;
30-

0 commit comments

Comments
 (0)