Skip to content

Commit e0225f9

Browse files
Auth/PM-34130 - Fix DeviceAuthDetails constructor and stored procedure for EDD compliance (#7416)
* 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. * PM-34130 - Fix EF constructor in DeviceAuthDetails to copy all Device fields Copy UserId, PushToken, RevisionDate, EncryptedPrivateKey, and Active from the source Device in the EF constructor. Previously these fields were omitted, causing IsTrusted to always return false for EF-sourced results. * PM-34130 - PR feedback resolution * PM-34130 - Fix migration sort from main merge
1 parent e4bf05c commit e0225f9

5 files changed

Lines changed: 321 additions & 91 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: 14 additions & 54 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.
@@ -24,62 +28,18 @@ public DeviceAuthDetails(
2428
}
2529

2630
Id = device.Id;
31+
UserId = device.UserId;
2732
Name = device.Name;
2833
Type = device.Type;
2934
Identifier = device.Identifier;
35+
PushToken = device.PushToken;
3036
CreationDate = device.CreationDate;
31-
IsTrusted = device.IsTrusted();
32-
EncryptedPublicKey = device.EncryptedPublicKey;
37+
RevisionDate = device.RevisionDate;
3338
EncryptedUserKey = device.EncryptedUserKey;
39+
EncryptedPublicKey = device.EncryptedPublicKey;
40+
EncryptedPrivateKey = device.EncryptedPrivateKey;
41+
Active = device.Active;
3442
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;
43+
AuthRequestCreationDate = authRequestCreationDate;
8444
}
8545
}

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

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,38 @@ 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
24+
[dbo].[DeviceView] D
25+
LEFT OUTER JOIN (
26+
SELECT
27+
[Id],
28+
[CreationDate],
29+
[RequestDeviceIdentifier],
30+
[Approved],
31+
ROW_NUMBER() OVER (PARTITION BY [RequestDeviceIdentifier] ORDER BY [CreationDate] DESC) AS rn
32+
FROM
33+
[dbo].[AuthRequestView]
34+
WHERE
35+
[Type] IN (0,1) -- AuthenticateAndUnlock and Unlock types only
36+
AND [CreationDate] >= DATEADD(MINUTE, -@ExpirationMinutes, GETUTCDATE()) -- Ensure the request hasn't expired
37+
AND [UserId] = @UserId -- Requests for this user only
38+
) AR -- This join will get the most recent request per device, regardless of approval status
39+
ON D.[Identifier] = AR.[RequestDeviceIdentifier] AND AR.[rn] = 1 AND AR.[Approved] IS NULL -- Get only the most recent unapproved request per device
2640
WHERE
27-
D.UserId = @UserId -- Include only devices for this user
28-
AND D.Active = 1; -- Include only active devices
41+
D.[UserId] = @UserId -- Include only devices for this user
42+
AND D.[Active] = 1; -- Include only active devices
2943
END;
30-

0 commit comments

Comments
 (0)