Skip to content

Commit 446315d

Browse files
authored
Merge pull request #189 from detunized/copilot/add-created-updated-dates
1Password: Expose CreatedAt and UpdatedAt timestamps as DateTime on VaultItem
2 parents 3f8055e + d573747 commit 446315d

File tree

6 files changed

+129
-1
lines changed

6 files changed

+129
-1
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## 28.2.0
4+
- 1Password: added `VaultItem.CreatedAt` and `VaultItem.UpdatedAt` properties
5+
36
## 28.1.0
47
- 1Password: fixed incorrect item and vault IDs detection in Client.GetItem
58
- 1Password: added `NoItem.Inaccessible` for inaccessible vaults and items

src/OnePassword/Response.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,12 @@ internal class VaultItem
313313
[JsonProperty("trashed", Required = Required.Always)]
314314
public readonly string Deleted;
315315

316+
[JsonProperty("createdAt")]
317+
public readonly string CreatedAt;
318+
319+
[JsonProperty("updatedAt")]
320+
public readonly string UpdatedAt;
321+
316322
[JsonProperty("itemVersion", Required = Required.Always)]
317323
public readonly int Version;
318324

src/OnePassword/VaultItem.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#nullable enable
55

6+
using System;
67
using System.Collections.Generic;
78
using R = PasswordManagerAccess.OnePassword.Response;
89

@@ -17,6 +18,8 @@ public class VaultItem
1718
public string Name => Overview.Title ?? "";
1819
public string Description => Overview.AdditionalInfo ?? "";
1920
public string Note => Details.Note ?? "";
21+
public DateTime CreatedAt => _createdAt ??= ParseDateTime(_itemInfo.CreatedAt);
22+
public DateTime UpdatedAt => _updatedAt ??= ParseDateTime(_itemInfo.UpdatedAt);
2023

2124
public Field[] Fields => _fields ??= ParseFields();
2225

@@ -72,6 +75,19 @@ internal string FindField(string name)
7275
return "";
7376
}
7477

78+
internal static DateTime ParseDateTime(string dateString)
79+
{
80+
if (string.IsNullOrWhiteSpace(dateString))
81+
return default;
82+
83+
if (DateTime.TryParse(dateString, System.Globalization.CultureInfo.InvariantCulture,
84+
System.Globalization.DateTimeStyles.RoundtripKind, out var result))
85+
return result;
86+
87+
// Return default if parsing fails
88+
return default;
89+
}
90+
7591
//
7692
// Private
7793
//
@@ -81,4 +97,6 @@ internal string FindField(string name)
8197
private R.VaultItemOverview? _overview;
8298
private R.VaultItemDetails? _details;
8399
private Field[]? _fields;
100+
private DateTime? _createdAt;
101+
private DateTime? _updatedAt;
84102
}

src/PasswordManagerAccess.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<Company>detunized.net</Company>
99
<PackageLicenseExpression>MIT</PackageLicenseExpression>
1010
<!-- TODO: This needs to be either updated every time we release a new version or set in the CI -->
11-
<AssemblyVersion>28.1.0</AssemblyVersion>
11+
<AssemblyVersion>28.2.0</AssemblyVersion>
1212
</PropertyGroup>
1313

1414
<!-- USE_MITM_PROXY could be set to 1 in the global MSBuild settings in Rider IDE -->

test/OnePassword/ClientTest.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ public void GetItem_returns_account()
5454
// Assert
5555
result.TryPickT0(out var account, out _).ShouldBeTrue();
5656
account.Id.ShouldBe("wm3uxq4xsmb4mghxw6o3s7zrem");
57+
account.CreatedAt.ShouldBe(new DateTime(2016, 8, 4, 13, 15, 10, DateTimeKind.Utc));
58+
account.UpdatedAt.ShouldBe(new DateTime(2016, 8, 4, 13, 16, 7, DateTimeKind.Utc));
5759
}
5860

5961
[Fact]

test/OnePassword/ResponseTest.cs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright (C) Dmitry Yakimenko (detunized@gmail.com).
22
// Licensed under the terms of the MIT license. See LICENCE for details.
33

4+
using System;
45
using Newtonsoft.Json;
6+
using PasswordManagerAccess.OnePassword;
57
using Xunit;
68
using R = PasswordManagerAccess.OnePassword.Response;
79

@@ -18,5 +20,102 @@ public void VaultItemDetails_parses_with_all_types_of_fields()
1820
Assert.Equal(6, details.Sections[0].Fields.Length);
1921
Assert.Equal(6, details.Sections[1].Fields.Length);
2022
}
23+
24+
[Fact]
25+
public void VaultItem_parses_createdAt_and_updatedAt()
26+
{
27+
var json = GetFixture("get-vault-item-response");
28+
var response = JsonConvert.DeserializeObject<R.SingleVaultItem>(json);
29+
Assert.Equal("2016-08-04T13:15:10Z", response.Item.CreatedAt);
30+
Assert.Equal("2016-08-04T13:16:07Z", response.Item.UpdatedAt);
31+
}
32+
33+
[Fact]
34+
public void VaultItem_handles_missing_createdAt_and_updatedAt()
35+
{
36+
var json = @"{
37+
""uuid"": ""test-id"",
38+
""templateUuid"": ""001"",
39+
""trashed"": ""N"",
40+
""itemVersion"": 1,
41+
""encryptedBy"": ""test-key"",
42+
""encOverview"": {
43+
""kid"": ""test-key"",
44+
""enc"": ""A256GCM"",
45+
""cty"": ""b5+jwk+json"",
46+
""iv"": ""test-iv"",
47+
""data"": ""test-data""
48+
},
49+
""encDetails"": {
50+
""kid"": ""test-key"",
51+
""enc"": ""A256GCM"",
52+
""cty"": ""b5+jwk+json"",
53+
""iv"": ""test-iv"",
54+
""data"": ""test-data""
55+
}
56+
}";
57+
var item = JsonConvert.DeserializeObject<R.VaultItem>(json);
58+
Assert.Null(item.CreatedAt);
59+
Assert.Null(item.UpdatedAt);
60+
}
61+
62+
[Fact]
63+
public void VaultItem_handles_invalid_createdAt_and_updatedAt()
64+
{
65+
var json = @"{
66+
""uuid"": ""test-id"",
67+
""templateUuid"": ""001"",
68+
""trashed"": ""N"",
69+
""createdAt"": ""not-a-date"",
70+
""updatedAt"": ""also-not-a-date"",
71+
""itemVersion"": 1,
72+
""encryptedBy"": ""test-key"",
73+
""encOverview"": {
74+
""kid"": ""test-key"",
75+
""enc"": ""A256GCM"",
76+
""cty"": ""b5+jwk+json"",
77+
""iv"": ""test-iv"",
78+
""data"": ""test-data""
79+
},
80+
""encDetails"": {
81+
""kid"": ""test-key"",
82+
""enc"": ""A256GCM"",
83+
""cty"": ""b5+jwk+json"",
84+
""iv"": ""test-iv"",
85+
""data"": ""test-data""
86+
}
87+
}";
88+
var item = JsonConvert.DeserializeObject<R.VaultItem>(json);
89+
Assert.Equal("not-a-date", item.CreatedAt);
90+
Assert.Equal("also-not-a-date", item.UpdatedAt);
91+
}
92+
93+
[Fact]
94+
public void VaultItem_ParseDateTime_parses_valid_date()
95+
{
96+
var result = VaultItem.ParseDateTime("2016-08-04T13:15:10Z");
97+
Assert.Equal(new DateTime(2016, 8, 4, 13, 15, 10, DateTimeKind.Utc), result);
98+
}
99+
100+
[Fact]
101+
public void VaultItem_ParseDateTime_returns_default_for_null()
102+
{
103+
var result = VaultItem.ParseDateTime(null);
104+
Assert.Equal(default(DateTime), result);
105+
}
106+
107+
[Fact]
108+
public void VaultItem_ParseDateTime_returns_default_for_empty()
109+
{
110+
var result = VaultItem.ParseDateTime("");
111+
Assert.Equal(default(DateTime), result);
112+
}
113+
114+
[Fact]
115+
public void VaultItem_ParseDateTime_returns_default_for_invalid()
116+
{
117+
var result = VaultItem.ParseDateTime("not-a-date");
118+
Assert.Equal(default(DateTime), result);
119+
}
21120
}
22121
}

0 commit comments

Comments
 (0)