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); + } } }