From d6776a51fa429e1b1b4d944b797d62284b406fb0 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Fri, 28 Jul 2023 16:01:59 -0400 Subject: [PATCH 1/4] chore: add logic + tests for cache invalidation --- src/CommonLib/Cache.cs | 36 +++++++++++++++++++++++++--- test/unit/CacheTest.cs | 54 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 test/unit/CacheTest.cs diff --git a/src/CommonLib/Cache.cs b/src/CommonLib/Cache.cs index bf311aeb..5dcae41a 100644 --- a/src/CommonLib/Cache.cs +++ b/src/CommonLib/Cache.cs @@ -1,4 +1,6 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; +using System.ComponentModel; using System.Runtime.Serialization; using SharpHoundCommonLib.Enums; @@ -18,6 +20,8 @@ public class Cache // // [DataMember]private ConcurrentDictionary _valueToIDCache; + private static Version defaultVersion = new Version(1, 0, 0); + private Cache() { ValueToIdCache = new ConcurrentDictionary(); @@ -36,6 +40,8 @@ private Cache() [DataMember] public ConcurrentDictionary SIDToDomainCache { get; private set; } [DataMember] public ConcurrentDictionary ValueToIdCache { get; private set; } + [DataMember] public DateTime CacheCreationDate { get; set; } + [DataMember] public Version CacheCreationVersion { get; set; } [IgnoreDataMember] private static Cache CacheInstance { get; set; } @@ -137,9 +143,17 @@ private static string GetPrefixKey(string key, string domain) /// Creates a new empty cache instance /// /// - public static Cache CreateNewCache() + public static Cache CreateNewCache(Version version = null) { - return new Cache(); + if (version == null) + { + version = new Version(1, 0, 0); + } + return new Cache + { + CacheCreationVersion = version, + CacheCreationDate = DateTime.Now.Date + }; } /// @@ -152,6 +166,22 @@ public static void SetCacheInstance(Cache cache) CreateMissingDictionaries(); } + public static bool CacheNeedsInvalidation(Cache cache, Version version) + { + var threshold = DateTime.Now.Subtract(TimeSpan.FromDays(30)); + if (cache.CacheCreationDate < threshold) + { + return true; + } + + if (cache.CacheCreationVersion == null || version > cache.CacheCreationVersion) + { + return true; + } + + return false; + } + /// /// Gets stats from the currently loaded cache /// diff --git a/test/unit/CacheTest.cs b/test/unit/CacheTest.cs new file mode 100644 index 00000000..147b5eaa --- /dev/null +++ b/test/unit/CacheTest.cs @@ -0,0 +1,54 @@ +using System; +using Newtonsoft.Json; +using SharpHoundCommonLib; +using Xunit; +using Xunit.Abstractions; + +namespace CommonLibTest; + +public class CacheTest +{ + private ITestOutputHelper _testOutputHelper; + public CacheTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + + [Fact] + public void Cache_TestCacheInvalidation() + { + var cache = Cache.CreateNewCache(); + var version = new Version(1, 0, 0); + cache.CacheCreationVersion = version; + + Assert.True(Cache.CacheNeedsInvalidation(cache, new Version(1,0,1))); + Assert.False(Cache.CacheNeedsInvalidation(cache, new Version(1,0,0))); + + var time = DateTime.Now.Subtract(TimeSpan.FromDays(29)); + cache.CacheCreationDate = time; + Assert.False(Cache.CacheNeedsInvalidation(cache, version)); + cache.CacheCreationDate = DateTime.Now.Subtract(TimeSpan.FromDays(31)); + Assert.True(Cache.CacheNeedsInvalidation(cache, version)); + } + + [Fact] + public void Cache_TestNewCache() + { + var cache = Cache.CreateNewCache(); + Assert.Equal(cache.CacheCreationVersion, new Version(1,0,0)); + var version = new Version(1, 0, 1); + cache = Cache.CreateNewCache(version); + var time = DateTime.Now.Date; + Assert.Equal(cache.CacheCreationVersion, version); + Assert.Equal(cache.CacheCreationDate, time); + } + + [Fact] + public void Cache_OldCacheWillInvalidate() + { + const string json = """{"GlobalCatalogCache": {}, "IdToTypeCache": {}, "MachineSidCache": {}, SIDToDomainCache: {}, "ValueToIdCache": {}}"""; + var cache = JsonConvert.DeserializeObject(json); + Assert.Null(cache.CacheCreationVersion); + Assert.True(Cache.CacheNeedsInvalidation(cache, new Version(1,0,0))); + } +} \ No newline at end of file From 3b884ba6b25e3874b2645926a7f3b4f96c3eb580 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Fri, 28 Jul 2023 16:15:14 -0400 Subject: [PATCH 2/4] chore: fix file-scoped namespace --- test/unit/CacheTest.cs | 83 +++++++++++++++++++++--------------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/test/unit/CacheTest.cs b/test/unit/CacheTest.cs index 147b5eaa..eaa047d4 100644 --- a/test/unit/CacheTest.cs +++ b/test/unit/CacheTest.cs @@ -4,51 +4,52 @@ using Xunit; using Xunit.Abstractions; -namespace CommonLibTest; - -public class CacheTest +namespace CommonLibTest { - private ITestOutputHelper _testOutputHelper; - public CacheTest(ITestOutputHelper testOutputHelper) - { - _testOutputHelper = testOutputHelper; - } - - [Fact] - public void Cache_TestCacheInvalidation() + public class CacheTest { - var cache = Cache.CreateNewCache(); - var version = new Version(1, 0, 0); - cache.CacheCreationVersion = version; + private ITestOutputHelper _testOutputHelper; + public CacheTest(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } - Assert.True(Cache.CacheNeedsInvalidation(cache, new Version(1,0,1))); - Assert.False(Cache.CacheNeedsInvalidation(cache, new Version(1,0,0))); + [Fact] + public void Cache_TestCacheInvalidation() + { + var cache = Cache.CreateNewCache(); + var version = new Version(1, 0, 0); + cache.CacheCreationVersion = version; + + Assert.True(Cache.CacheNeedsInvalidation(cache, new Version(1,0,1))); + Assert.False(Cache.CacheNeedsInvalidation(cache, new Version(1,0,0))); - var time = DateTime.Now.Subtract(TimeSpan.FromDays(29)); - cache.CacheCreationDate = time; - Assert.False(Cache.CacheNeedsInvalidation(cache, version)); - cache.CacheCreationDate = DateTime.Now.Subtract(TimeSpan.FromDays(31)); - Assert.True(Cache.CacheNeedsInvalidation(cache, version)); - } + var time = DateTime.Now.Subtract(TimeSpan.FromDays(29)); + cache.CacheCreationDate = time; + Assert.False(Cache.CacheNeedsInvalidation(cache, version)); + cache.CacheCreationDate = DateTime.Now.Subtract(TimeSpan.FromDays(31)); + Assert.True(Cache.CacheNeedsInvalidation(cache, version)); + } - [Fact] - public void Cache_TestNewCache() - { - var cache = Cache.CreateNewCache(); - Assert.Equal(cache.CacheCreationVersion, new Version(1,0,0)); - var version = new Version(1, 0, 1); - cache = Cache.CreateNewCache(version); - var time = DateTime.Now.Date; - Assert.Equal(cache.CacheCreationVersion, version); - Assert.Equal(cache.CacheCreationDate, time); - } + [Fact] + public void Cache_TestNewCache() + { + var cache = Cache.CreateNewCache(); + Assert.Equal(cache.CacheCreationVersion, new Version(1,0,0)); + var version = new Version(1, 0, 1); + cache = Cache.CreateNewCache(version); + var time = DateTime.Now.Date; + Assert.Equal(cache.CacheCreationVersion, version); + Assert.Equal(cache.CacheCreationDate, time); + } - [Fact] - public void Cache_OldCacheWillInvalidate() - { - const string json = """{"GlobalCatalogCache": {}, "IdToTypeCache": {}, "MachineSidCache": {}, SIDToDomainCache: {}, "ValueToIdCache": {}}"""; - var cache = JsonConvert.DeserializeObject(json); - Assert.Null(cache.CacheCreationVersion); - Assert.True(Cache.CacheNeedsInvalidation(cache, new Version(1,0,0))); + [Fact] + public void Cache_OldCacheWillInvalidate() + { + const string json = """{"GlobalCatalogCache": {}, "IdToTypeCache": {}, "MachineSidCache": {}, SIDToDomainCache: {}, "ValueToIdCache": {}}"""; + var cache = JsonConvert.DeserializeObject(json); + Assert.Null(cache.CacheCreationVersion); + Assert.True(Cache.CacheNeedsInvalidation(cache, new Version(1,0,0))); + } } -} \ No newline at end of file +} From 68462b2a549705477ffde4db8d9477ba703ecced Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 31 Jul 2023 10:11:05 -0400 Subject: [PATCH 3/4] chore: fix test string --- test/unit/CacheTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/CacheTest.cs b/test/unit/CacheTest.cs index eaa047d4..87e2f0be 100644 --- a/test/unit/CacheTest.cs +++ b/test/unit/CacheTest.cs @@ -46,7 +46,7 @@ public void Cache_TestNewCache() [Fact] public void Cache_OldCacheWillInvalidate() { - const string json = """{"GlobalCatalogCache": {}, "IdToTypeCache": {}, "MachineSidCache": {}, SIDToDomainCache: {}, "ValueToIdCache": {}}"""; + const string json = "{\"GlobalCatalogCache\": {}, \"IdToTypeCache\": {}, \"MachineSidCache\": {}, \"SIDToDomainCache\": {}, \"ValueToIdCache\": {}}"; var cache = JsonConvert.DeserializeObject(json); Assert.Null(cache.CacheCreationVersion); Assert.True(Cache.CacheNeedsInvalidation(cache, new Version(1,0,0))); From 3fee18309397c3dcde422461c86647a79499cc87 Mon Sep 17 00:00:00 2001 From: rvazarkar Date: Mon, 31 Jul 2023 13:18:30 -0400 Subject: [PATCH 4/4] chore: properly use default version --- src/CommonLib/Cache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CommonLib/Cache.cs b/src/CommonLib/Cache.cs index 5dcae41a..deaa23ea 100644 --- a/src/CommonLib/Cache.cs +++ b/src/CommonLib/Cache.cs @@ -20,7 +20,7 @@ public class Cache // // [DataMember]private ConcurrentDictionary _valueToIDCache; - private static Version defaultVersion = new Version(1, 0, 0); + private static Version defaultVersion = new(1, 0, 0); private Cache() { @@ -147,7 +147,7 @@ public static Cache CreateNewCache(Version version = null) { if (version == null) { - version = new Version(1, 0, 0); + version = defaultVersion; } return new Cache {