diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5b973546..41b41c2d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
# Changelog
+## 28.2.0
+ - 1Password: added `VaultItem.CreatedAt` and `VaultItem.UpdatedAt` properties
+
## 28.1.0
- 1Password: fixed incorrect item and vault IDs detection in Client.GetItem
- 1Password: added `NoItem.Inaccessible` for inaccessible vaults and items
diff --git a/src/OnePassword/Response.cs b/src/OnePassword/Response.cs
index 0b92ae50..e5d5f067 100644
--- a/src/OnePassword/Response.cs
+++ b/src/OnePassword/Response.cs
@@ -313,6 +313,12 @@ internal class VaultItem
[JsonProperty("trashed", Required = Required.Always)]
public readonly string Deleted;
+ [JsonProperty("createdAt")]
+ public readonly string CreatedAt;
+
+ [JsonProperty("updatedAt")]
+ public readonly string UpdatedAt;
+
[JsonProperty("itemVersion", Required = Required.Always)]
public readonly int Version;
diff --git a/src/OnePassword/VaultItem.cs b/src/OnePassword/VaultItem.cs
index c0765227..54904704 100644
--- a/src/OnePassword/VaultItem.cs
+++ b/src/OnePassword/VaultItem.cs
@@ -3,6 +3,7 @@
#nullable enable
+using System;
using System.Collections.Generic;
using R = PasswordManagerAccess.OnePassword.Response;
@@ -17,6 +18,8 @@ public class VaultItem
public string Name => Overview.Title ?? "";
public string Description => Overview.AdditionalInfo ?? "";
public string Note => Details.Note ?? "";
+ public DateTime CreatedAt => _createdAt ??= ParseDateTime(_itemInfo.CreatedAt);
+ public DateTime UpdatedAt => _updatedAt ??= ParseDateTime(_itemInfo.UpdatedAt);
public Field[] Fields => _fields ??= ParseFields();
@@ -72,6 +75,19 @@ internal string FindField(string name)
return "";
}
+ internal static DateTime ParseDateTime(string dateString)
+ {
+ if (string.IsNullOrWhiteSpace(dateString))
+ return default;
+
+ if (DateTime.TryParse(dateString, System.Globalization.CultureInfo.InvariantCulture,
+ System.Globalization.DateTimeStyles.RoundtripKind, out var result))
+ return result;
+
+ // Return default if parsing fails
+ return default;
+ }
+
//
// Private
//
@@ -81,4 +97,6 @@ internal string FindField(string name)
private R.VaultItemOverview? _overview;
private R.VaultItemDetails? _details;
private Field[]? _fields;
+ private DateTime? _createdAt;
+ private DateTime? _updatedAt;
}
diff --git a/src/PasswordManagerAccess.csproj b/src/PasswordManagerAccess.csproj
index b4010205..95a7f2b5 100644
--- a/src/PasswordManagerAccess.csproj
+++ b/src/PasswordManagerAccess.csproj
@@ -8,7 +8,7 @@
detunized.net
MIT
- 28.1.0
+ 28.2.0
diff --git a/test/OnePassword/ClientTest.cs b/test/OnePassword/ClientTest.cs
index 050b80de..c57503f3 100644
--- a/test/OnePassword/ClientTest.cs
+++ b/test/OnePassword/ClientTest.cs
@@ -54,6 +54,8 @@ public void GetItem_returns_account()
// Assert
result.TryPickT0(out var account, out _).ShouldBeTrue();
account.Id.ShouldBe("wm3uxq4xsmb4mghxw6o3s7zrem");
+ account.CreatedAt.ShouldBe(new DateTime(2016, 8, 4, 13, 15, 10, DateTimeKind.Utc));
+ account.UpdatedAt.ShouldBe(new DateTime(2016, 8, 4, 13, 16, 7, DateTimeKind.Utc));
}
[Fact]
diff --git a/test/OnePassword/ResponseTest.cs b/test/OnePassword/ResponseTest.cs
index c7208f9d..372dcf91 100644
--- a/test/OnePassword/ResponseTest.cs
+++ b/test/OnePassword/ResponseTest.cs
@@ -1,7 +1,9 @@
// Copyright (C) Dmitry Yakimenko (detunized@gmail.com).
// Licensed under the terms of the MIT license. See LICENCE for details.
+using System;
using Newtonsoft.Json;
+using PasswordManagerAccess.OnePassword;
using Xunit;
using R = PasswordManagerAccess.OnePassword.Response;
@@ -18,5 +20,102 @@ public void VaultItemDetails_parses_with_all_types_of_fields()
Assert.Equal(6, details.Sections[0].Fields.Length);
Assert.Equal(6, details.Sections[1].Fields.Length);
}
+
+ [Fact]
+ public void VaultItem_parses_createdAt_and_updatedAt()
+ {
+ var json = GetFixture("get-vault-item-response");
+ var response = JsonConvert.DeserializeObject(json);
+ Assert.Equal("2016-08-04T13:15:10Z", response.Item.CreatedAt);
+ Assert.Equal("2016-08-04T13:16:07Z", response.Item.UpdatedAt);
+ }
+
+ [Fact]
+ public void VaultItem_handles_missing_createdAt_and_updatedAt()
+ {
+ var json = @"{
+ ""uuid"": ""test-id"",
+ ""templateUuid"": ""001"",
+ ""trashed"": ""N"",
+ ""itemVersion"": 1,
+ ""encryptedBy"": ""test-key"",
+ ""encOverview"": {
+ ""kid"": ""test-key"",
+ ""enc"": ""A256GCM"",
+ ""cty"": ""b5+jwk+json"",
+ ""iv"": ""test-iv"",
+ ""data"": ""test-data""
+ },
+ ""encDetails"": {
+ ""kid"": ""test-key"",
+ ""enc"": ""A256GCM"",
+ ""cty"": ""b5+jwk+json"",
+ ""iv"": ""test-iv"",
+ ""data"": ""test-data""
+ }
+ }";
+ var item = JsonConvert.DeserializeObject(json);
+ Assert.Null(item.CreatedAt);
+ Assert.Null(item.UpdatedAt);
+ }
+
+ [Fact]
+ public void VaultItem_handles_invalid_createdAt_and_updatedAt()
+ {
+ var json = @"{
+ ""uuid"": ""test-id"",
+ ""templateUuid"": ""001"",
+ ""trashed"": ""N"",
+ ""createdAt"": ""not-a-date"",
+ ""updatedAt"": ""also-not-a-date"",
+ ""itemVersion"": 1,
+ ""encryptedBy"": ""test-key"",
+ ""encOverview"": {
+ ""kid"": ""test-key"",
+ ""enc"": ""A256GCM"",
+ ""cty"": ""b5+jwk+json"",
+ ""iv"": ""test-iv"",
+ ""data"": ""test-data""
+ },
+ ""encDetails"": {
+ ""kid"": ""test-key"",
+ ""enc"": ""A256GCM"",
+ ""cty"": ""b5+jwk+json"",
+ ""iv"": ""test-iv"",
+ ""data"": ""test-data""
+ }
+ }";
+ var item = JsonConvert.DeserializeObject(json);
+ Assert.Equal("not-a-date", item.CreatedAt);
+ Assert.Equal("also-not-a-date", item.UpdatedAt);
+ }
+
+ [Fact]
+ public void VaultItem_ParseDateTime_parses_valid_date()
+ {
+ var result = VaultItem.ParseDateTime("2016-08-04T13:15:10Z");
+ Assert.Equal(new DateTime(2016, 8, 4, 13, 15, 10, DateTimeKind.Utc), result);
+ }
+
+ [Fact]
+ public void VaultItem_ParseDateTime_returns_default_for_null()
+ {
+ var result = VaultItem.ParseDateTime(null);
+ Assert.Equal(default(DateTime), result);
+ }
+
+ [Fact]
+ public void VaultItem_ParseDateTime_returns_default_for_empty()
+ {
+ var result = VaultItem.ParseDateTime("");
+ Assert.Equal(default(DateTime), result);
+ }
+
+ [Fact]
+ public void VaultItem_ParseDateTime_returns_default_for_invalid()
+ {
+ var result = VaultItem.ParseDateTime("not-a-date");
+ Assert.Equal(default(DateTime), result);
+ }
}
}